0

I have been looking around for a way to set the alignment of the segmented control titles to the left but I don't seem to be able to achieve what I want.

I have created this little function to change the frame of the subviews of the segment control. It works at first.

    func modifyFrameOfSegment() {
       for segment in segmentedControl.subviews {
          guard segment.subviews.isNotEmpty else { return }
          segment.contentMode = .left
           for label in segment.subviews where label is UILabel {
              label.frame = CGRect(x: 0, y: label.frame.origin.y, width: label.frame.size.width, height: label.frame.size.height)
              (label as! UILabel).textAlignment = .left
           }
       }
    }

But everytime I select a new segment it resets the frames of all the subviews and center align all the titles again.

Is there a way to achieve a permanent left alignment for the segment titles in a segmented control?

Any tips or advice would be greatly appreciated.

Thank you for your time.

Martin
  • 843
  • 8
  • 17
  • hi @Martin, please try to call this function in the viewDidLayoutSubviews. Let me know if it works. – Vikash Sinha Sep 19 '19 at 16:33
  • You can also achieve similar functionality by taking button in stackview and setting button titles to left but totally depend if it suits your requirements. Let me clear on the point what are you trying to achieve. – Vikash Sinha Sep 19 '19 at 16:36
  • @VikashSinha: Hello Vikash I have already tried. As I said It work when the segmented control first appears but first time I select a new segment in the segmented control it centers all the titles again. I can't seem to find a way no to have this happening. – Martin Sep 19 '19 at 16:37

2 Answers2

1

Let's use this method

self.segmentedControl.setContentPositionAdjustment(UIOffset(horizontal: -20, vertical: 0), forSegmentType: .left, barMetrics: .default)

And you can do what you want (Of course, you can change the horizontal & vertical value by your needs). Here is the result:

enter image description here

jacob
  • 1,024
  • 9
  • 14
  • This is not really what I'm looking for. Here you are just changing the offsets of the titles. It could work if all the segment titles had the same offset but it is not the case. I'm looking for a generic way to left align all the title. – Martin Sep 20 '19 at 08:12
  • Downvote is unfair when i was trying to help you to solve the problem – jacob Sep 23 '19 at 02:09
  • didn't mean to do that sorry about it – Martin Sep 23 '19 at 08:37
  • 1
    @Jacob Never assume that you know who downvoted. There's nothing unfair about a downvote on an answer that misses the mark, no matter how good the author's intentions are. Votes aren't a reward for trying; they're an indication of the usefulness of the answer. In this case, the picture in your answer shows why your solution isn't what the OP asked for: the second item is still centered. – Caleb Sep 23 '19 at 13:59
1

Update:

There's apparently no way to set the alignment of the items, but you can fake it by adjusting the position of each individual item using setContentOffset(_ offset: CGSize, forSegmentAt segment: Int). Here's a kludgy example:

class LeftSegmentedControl: UISegmentedControl {

    var margin : CGFloat = 10

    override func layoutSubviews() {
        super.layoutSubviews()
        leftJustifyItems()
    }

    func leftJustifyItems() {
        let fontAttributes = titleTextAttributes(for: .normal)
        let segments = numberOfSegments - 1
        let controlWidth = frame.size.width
        let segmentWidth = controlWidth / CGFloat(numberOfSegments)
        for segment in 0...segments {
            let title = titleForSegment(at: segment)
            setWidth(segmentWidth, forSegmentAt: segment)
            if let t = title {
                let titleSize = t.size(withAttributes: fontAttributes)
                let offset = (segmentWidth - titleSize.width) / 2 - margin
                self.setContentOffset(CGSize(width: -offset, height: 0), forSegmentAt: segment)
            }
        }
    }
}

Here's what it looks like:

There are a few caveats:

  • This version sets the segments to all have equal width, which might not be what you want.

  • I used a fixed left margin of 10px because it seems unlikely that you'd want to vary that, but you can obviously change it or make it a settable property.

  • Just because you can do this doesn't mean you should. Personally, I don't think it looks great, and it suffers in the usability department too. Users expect segmented control items to be centered, and left-justifying the items will make it harder for them to know where to tap to hit the segment. That seems particularly true for short items like the one labelled "3rd" in the example. It's not terrible, it just seems a little weird.

Original answer:

UIControl (of which UISegmentedControl is a subclass) has a contentHorizontalAlignment property that's supposed to tell the control to align its content a certain way, so the logical thing to do would be to set it like this:

let segmented = UISegmentedControl(items: ["Yes", "No", "Maybe"])
segmented.frame = CGRect(x:75, y:250, width:250, height:35)
segmented.contentHorizontalAlignment = .left

But that doesn't work — you still get the labels centered. If you've got a compelling use case for left-aligned segments, you should send the request to Apple.

One way you could work around this problem is to render your labels into images and then use the images as the segment labels instead of plain strings. Starting from the code in How to convert a UIView to an image, you could easily subclass UISegmentedControl to create images from the item strings.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • I had tried this solution also. Didn't work as you point out. I'll maybe create a bug report for Apple to investigate. Thank anyways – Martin Sep 23 '19 at 08:42