NSSegmentedControl in darkmode

I use an NSSegmentedControl "on top of" an NSBox in my App in a couple of places. (Text only, no images) In Light Mode, the control draws on top of the lines of the NSBox so it looks like the lines go behind the Control. The unselected segments are white (probably opaque) and it covers up the box lines. In Dark Mode however, it looks like the *un*selected segments are now transparent and so the lines from the NSBox show thru the unselected segments. The selected segment is completely opaque though and so the NSBox line isn't seen.


What's the best way to "override" this transparency to get the same Light mode behaviour? I was hoping that I could assign a CA layer property/filter of some sort to have it always draw opaque? Or is my only solution to subclass and write my own drawRect:? Or?

Answered by janice in 398972022

Thanks for the suggestion Claude31. I spent some time subclassing NSSegmentedCell and doing my own drawing similar to your example. However it didn't work very well. Unlike you, I just drew a background color and then called 'super' to get the text drawn and to get the same look as the default. After fiddling with modifications to the origin and size, you could still see elements of the horizontal line around the vertical line drawn between the segments. If I made it wider, then the vertical lines were gone. I also needed to draw in the selected segment as you could see the horizontal line when my App was not the "Active" App (the highlighting goes away). And since I used this method in a couple of different places, I found that the "fiddling" needed to be different in each case since the width of the segment and it's associated padding around the text was completely different each instance.


But as part of mucking around, I realized a much easier solution. I just put an NSView behind the NSSegmentedControl and in front of the NSBox. Made it the same width and height as the control (and alignment) and set it's colour to the same as the background around the NSBox. Viola! No more NSBox showing through.


Hope that helps someone else later.

Yes, I observe this as well when the segmented control overlaps the box (or anything behind in fact).


You could subclass segmentedControl to have a specific draw method where you would set the color (or just the alpha value ?) of unselected cell to what you need:


https://stackoverflow.com/questions/9952308/nssegmentedcontrol-colors

Here is the subclass I used to draw correctly in darkmode as well.


May be you'll have to tweak some constants for origin and size of outlineRect to match perfectly.

I changed NSSegmentedCell type in IB accordingly.


class coloredSegmentedCell: NSSegmentedCell {
  
    override func drawSegment(_ segment: Int, inFrame frame: NSRect, with controlView: NSView) {
      
        super.drawSegment(segment, inFrame: frame,  with:controlView)
        if !self.isSelected(forSegment: segment) {
            let color = NSColor.white
            color.setFill()
            frame.fill()
            var outlineRect = frame
            outlineRect.origin.x -= 0.5
            outlineRect.size.width += 0.5
            outlineRect.origin.y += 0.5
            outlineRect.size.height += 1.0

            let bPath: NSBezierPath = NSBezierPath(rect: outlineRect)
            let borderColor = NSColor.systemGray // lightGray
            borderColor.setStroke()
            bPath.lineWidth = 0.5
            bPath.stroke()
        }
    }
}


Thanks to report if that works for you and close the thread.

Accepted Answer

Thanks for the suggestion Claude31. I spent some time subclassing NSSegmentedCell and doing my own drawing similar to your example. However it didn't work very well. Unlike you, I just drew a background color and then called 'super' to get the text drawn and to get the same look as the default. After fiddling with modifications to the origin and size, you could still see elements of the horizontal line around the vertical line drawn between the segments. If I made it wider, then the vertical lines were gone. I also needed to draw in the selected segment as you could see the horizontal line when my App was not the "Active" App (the highlighting goes away). And since I used this method in a couple of different places, I found that the "fiddling" needed to be different in each case since the width of the segment and it's associated padding around the text was completely different each instance.


But as part of mucking around, I realized a much easier solution. I just put an NSView behind the NSSegmentedControl and in front of the NSBox. Made it the same width and height as the control (and alignment) and set it's colour to the same as the background around the NSBox. Viola! No more NSBox showing through.


Hope that helps someone else later.

Good trick.


Don't forget to close the thread.

NSSegmentedControl in darkmode
 
 
Q