@@ -23,15 +23,21 @@ final class CustomSegmentedControl: UISegmentedControl {
2323 private lazy var underbar : UIView = makeUnderbar ( )
2424 private var underbarInfo : UnderbarInfo
2525 private var isFirstSettingDone = false
26+ private var itemSpacing : CGFloat = 24
2627 private var underline : Bool
28+ private var items : [ String ]
2729
2830 // MARK: - Init
2931
30- init ( items: [ Any ] ? , underbarInfo info: UnderbarInfo , underline: Bool = true ) {
32+ init ( items: [ String ] , underbarInfo info: UnderbarInfo , itemSpacing: CGFloat = 24 , underline: Bool = true ) {
33+ self . items = items
3134 self . underbarInfo = info
35+ self . itemSpacing = itemSpacing
3236 self . underline = underline
37+
3338 super. init ( items: items)
3439 setUI ( )
40+ setAddTarget ( )
3541 }
3642
3743 required init ? ( coder: NSCoder ) {
@@ -45,13 +51,34 @@ final class CustomSegmentedControl: UISegmentedControl {
4551
4652 if !isFirstSettingDone {
4753 isFirstSettingDone. toggle ( )
54+ setupItemBackgrounds ( )
4855 if underline {
4956 setUnderbarMovableBackgroundLayer ( )
5057 }
5158 layer. masksToBounds = false
5259 }
60+
61+ updateLabelColors ( )
5362 updateUnderbarPosition ( )
5463 }
64+
65+ override func hitTest( _ point: CGPoint , with event: UIEvent ? ) -> UIView ? {
66+ var xOffset : CGFloat = 0
67+
68+ for index in 0 ..< numberOfSegments {
69+ let textWidth = calculateTextWidth ( for: index)
70+ let segmentWidth = textWidth
71+ let segmentFrame = CGRect ( x: xOffset, y: 0 , width: segmentWidth, height: bounds. height)
72+
73+ if segmentFrame. contains ( point) {
74+ self . selectedSegmentIndex = index
75+ sendActions ( for: . valueChanged)
76+ return self
77+ }
78+ xOffset += ( segmentWidth + itemSpacing)
79+ }
80+ return nil
81+ }
5582}
5683
5784// MARK: - UI & Layout
@@ -60,20 +87,41 @@ private extension CustomSegmentedControl {
6087 private func setUI( ) {
6188 removeBorders ( )
6289 let normalTextAttributes : [ NSAttributedString . Key : Any ] = [
63- . foregroundColor: underbarInfo. backgroundColor,
64- . font: UIFont . title4
65- ]
66- let selectedTextAttributes : [ NSAttributedString . Key : Any ] = [
67- . foregroundColor: underbarInfo. highlightColor,
90+ . foregroundColor: UIColor . clear,
6891 . font: UIFont . title4
6992 ]
70-
7193 setTitleTextAttributes ( normalTextAttributes, for: . normal)
72- setTitleTextAttributes ( selectedTextAttributes, for: . selected)
7394 selectedSegmentTintColor = . clear
7495 selectedSegmentIndex = 0
7596 }
7697
98+ private func setAddTarget( ) {
99+ addTarget ( self , action: #selector( segmentValueChanged) , for: . valueChanged)
100+ }
101+
102+ private func setupItemBackgrounds( ) {
103+ for index in 0 ..< numberOfSegments {
104+ let frame = frameForSegment ( at: index)
105+ addBackgroundView ( for: frame, at: index)
106+ }
107+ }
108+
109+ private func makeUnderbar( ) -> UIView {
110+ let view = UIView ( frame: . zero)
111+ view. translatesAutoresizingMaskIntoConstraints = false
112+ view. backgroundColor = underbarInfo. highlightColor
113+ addSubview ( view)
114+ return view
115+ }
116+
117+ private func removeBorders( ) {
118+ let image = UIImage ( )
119+ setBackgroundImage ( image, for: . normal, barMetrics: . default)
120+ setBackgroundImage ( image, for: . selected, barMetrics: . default)
121+ setBackgroundImage ( image, for: . highlighted, barMetrics: . default)
122+ setDividerImage ( image, forLeftSegmentState: . normal, rightSegmentState: . normal, barMetrics: . default)
123+ }
124+
77125 private func setUnderbarMovableBackgroundLayer( ) {
78126 let backgroundLayer = CALayer ( )
79127 backgroundLayer. frame = . init(
@@ -86,14 +134,6 @@ private extension CustomSegmentedControl {
86134 layer. addSublayer ( backgroundLayer)
87135 }
88136
89- private func makeUnderbar( ) -> UIView {
90- let view = UIView ( frame: . zero)
91- view. translatesAutoresizingMaskIntoConstraints = false
92- view. backgroundColor = underbarInfo. highlightColor
93- addSubview ( view)
94- return view
95- }
96-
97137 private func updateUnderbarPosition( ) {
98138 let selectedSegmentFrame = frameForSegment ( at: selectedSegmentIndex)
99139 let textWidth = calculateTextWidth ( for: selectedSegmentIndex)
@@ -102,29 +142,59 @@ private extension CustomSegmentedControl {
102142 self . underbar. frame = CGRect (
103143 x: selectedSegmentFrame. origin. x + ( selectedSegmentFrame. width - textWidth) / 2 ,
104144 y: self . bounds. height - self . underbarInfo. height,
105- width: textWidth ,
145+ width: selectedSegmentFrame . width ,
106146 height: self . underbarInfo. height
107147 )
108148 } )
109149 }
110150
111- private func removeBorders( ) {
112- let image = UIImage ( )
113- setBackgroundImage ( image, for: . normal, barMetrics: . default)
114- setBackgroundImage ( image, for: . selected, barMetrics: . default)
115- setBackgroundImage ( image, for: . highlighted, barMetrics: . default)
116- setDividerImage ( image, forLeftSegmentState: . normal, rightSegmentState: . normal, barMetrics: . default)
151+ private func updateLabelColors( ) {
152+ for index in 0 ..< numberOfSegments {
153+ if let backgroundView = subviews. first ( where: { $0. tag == 999 + index } ) ,
154+ let label = backgroundView. subviews. first ( where: { $0 is UILabel } ) as? UILabel {
155+ label. textColor = ( selectedSegmentIndex == index) ? . terningMain : . grey300
156+ }
157+ }
117158 }
118159}
119160
120161// MARK: - Methods
121162
122163extension CustomSegmentedControl {
123164 private func frameForSegment( at index: Int ) -> CGRect {
124- let segmentWidth = bounds. width / CGFloat( numberOfSegments)
125- return CGRect ( x: CGFloat ( index) * segmentWidth, y: 0 , width: segmentWidth, height: bounds. height)
165+ let xOffset = ( 0 ..< index) . reduce ( 0 ) { result, idx in
166+ result + calculateTextWidth( for: idx) + itemSpacing
167+ }
168+ let itemWidth = calculateTextWidth ( for: index)
169+ return CGRect ( x: xOffset, y: 0 , width: itemWidth, height: bounds. height)
126170 }
127-
171+
172+ private func addBackgroundView( for frame: CGRect , at index: Int ) {
173+ subviews. filter { $0. tag == 999 + index } . forEach { $0. removeFromSuperview ( ) }
174+
175+ let textWidth = calculateTextWidth ( for: index)
176+ let segmentFrame = frameForSegment ( at: index)
177+
178+ let backgroundFrame = CGRect (
179+ x: segmentFrame. origin. x + ( segmentFrame. width - textWidth) / 2 ,
180+ y: 0 ,
181+ width: textWidth,
182+ height: bounds. height
183+ )
184+ let backgroundView = UIView ( frame: backgroundFrame)
185+ backgroundView. tag = 999 + index
186+
187+ let label = UILabel ( frame: backgroundView. bounds)
188+ label. text = titleForSegment ( at: index)
189+ label. textColor = ( index == selectedSegmentIndex) ? . terningMain : . grey300
190+ label. font = UIFont . title4
191+ label. textAlignment = . center
192+ label. tag = 1000 + index
193+
194+ backgroundView. addSubview ( label)
195+ insertSubview ( backgroundView, at: 0 )
196+ }
197+
128198 private func calculateTextWidth( for index: Int ) -> CGFloat {
129199 guard let title = titleForSegment ( at: index) ,
130200 let font = titleTextAttributes ( for: . normal) ? [ . font] as? UIFont else { return 0 }
@@ -133,3 +203,12 @@ extension CustomSegmentedControl {
133203 return size. width
134204 }
135205}
206+
207+ // MARK: - @objc func
208+
209+ extension CustomSegmentedControl {
210+ @objc
211+ private func segmentValueChanged( ) {
212+ updateLabelColors ( )
213+ }
214+ }
0 commit comments