@@ -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,45 +134,70 @@ 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)
100140
101- UIView . animate ( withDuration: 0.27 , delay: 0 , options: . curveEaseOut, animations: {
141+ underbar. layer. removeAllAnimations ( )
142+
143+ UIView . animate ( withDuration: 0.17 , delay: 0 , options: . curveLinear, animations: {
102144 self . underbar. frame = CGRect (
103145 x: selectedSegmentFrame. origin. x + ( selectedSegmentFrame. width - textWidth) / 2 ,
104146 y: self . bounds. height - self . underbarInfo. height,
105- width: textWidth ,
147+ width: selectedSegmentFrame . width ,
106148 height: self . underbarInfo. height
107149 )
150+ self . layoutIfNeeded ( )
108151 } )
109152 }
110153
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)
154+ private func updateLabelColors( ) {
155+ for index in 0 ..< numberOfSegments {
156+ if let backgroundView = subviews. first ( where: { $0. tag == 999 + index } ) ,
157+ let label = backgroundView. subviews. first ( where: { $0 is UILabel } ) as? UILabel {
158+ label. textColor = ( selectedSegmentIndex == index) ? . terningMain : . grey300
159+ }
160+ }
117161 }
118162}
119163
120164// MARK: - Methods
121165
122166extension CustomSegmentedControl {
123167 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)
168+ let xOffset = ( 0 ..< index) . reduce ( 0 ) { result, idx in
169+ result + calculateTextWidth( for: idx) + itemSpacing
170+ }
171+ let itemWidth = calculateTextWidth ( for: index)
172+ return CGRect ( x: xOffset, y: 0 , width: itemWidth, height: bounds. height)
126173 }
127-
174+
175+ private func addBackgroundView( for frame: CGRect , at index: Int ) {
176+ subviews. filter { $0. tag == 999 + index } . forEach { $0. removeFromSuperview ( ) }
177+
178+ let textWidth = calculateTextWidth ( for: index)
179+ let segmentFrame = frameForSegment ( at: index)
180+
181+ let backgroundFrame = CGRect (
182+ x: segmentFrame. origin. x + ( segmentFrame. width - textWidth) / 2 ,
183+ y: 0 ,
184+ width: textWidth,
185+ height: bounds. height
186+ )
187+ let backgroundView = UIView ( frame: backgroundFrame)
188+ backgroundView. tag = 999 + index
189+
190+ let label = UILabel ( frame: backgroundView. bounds)
191+ label. text = titleForSegment ( at: index)
192+ label. textColor = ( index == selectedSegmentIndex) ? . terningMain : . grey300
193+ label. font = UIFont . title4
194+ label. textAlignment = . center
195+ label. tag = 1000 + index
196+
197+ backgroundView. addSubview ( label)
198+ insertSubview ( backgroundView, at: 0 )
199+ }
200+
128201 private func calculateTextWidth( for index: Int ) -> CGFloat {
129202 guard let title = titleForSegment ( at: index) ,
130203 let font = titleTextAttributes ( for: . normal) ? [ . font] as? UIFont else { return 0 }
@@ -133,3 +206,12 @@ extension CustomSegmentedControl {
133206 return size. width
134207 }
135208}
209+
210+ // MARK: - @objc func
211+
212+ extension CustomSegmentedControl {
213+ @objc
214+ private func segmentValueChanged( ) {
215+ updateLabelColors ( )
216+ }
217+ }
0 commit comments