๊ฐœ๋ฐœ ์‹œํ–‰์ฐฉ์˜ค

[ iOS ์‹œํ–‰์ฐฉ์˜ค ]Custom Switch

Forest Yun 2022. 7. 21. 18:16
728x90

 

์›ํ•˜๋Š” switch ๋””์ž์ธ์ด Apple์˜ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” UISwitch์™€ ๋น„์Šทํ•˜๋ฉด์„œ๋„ ๋‹ฌ๋ผ ์ปค์Šคํ…€์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

 

 

UISwitch

 

testSwitch.transform = CGAffineTransform(scaleX: 2, y: 0.7)

์ฒ˜์Œ์—๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” UISwitch์˜ ํฌ๊ธฐ๋ฅผ ์ค„์—ฌ ํ•ด๊ฒฐํ•˜๊ณ ์ž ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ •๋ง ์‚ฌ์ด์ฆˆ๋งŒ ์กฐ์ •๋˜์–ด ์›ํ•˜๋Š” UI๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.

์ดํ›„๋กœ๋„ UISwitch๋ฅผ ์ปค์Šคํ…€ํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด์•˜์ง€๋งŒ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•๋งŒ ์ œ์‹œ๋˜์–ด ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ๊ฒ ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

 

 

UIView

switch๋ฅผ ์›ํ•˜๋Š” ๋””์ž์ธ์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๊ฐ€์žฅ ๊ทผ๋ณธ์ ์ธ UIView๋ฅผ ์ปค์Šคํ…€ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๋ฐ”ํƒ•์ด ๋˜๋Š” bar์™€ ์ž‘๋™๋˜๋Š” ๊ฑธ ๋ณด์—ฌ์ฃผ๋Š” ์›ํ˜•๋ฒ„ํŠผ์œผ๋กœ switch๋ฅผ ๊ตฌ๋ถ„ํ•ด view๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค. ์ด ๋‘ ๊ฐœ์˜ ๋ทฐ๋ฅผ ๊ฒน์ณ ํ•˜๋‚˜์˜ switch๋กœ ๋ณด์ด๊ฒŒ ๊ตฌ์„ฑํ•ด ์›ํ•˜๋Š” ๋””์ž์ธ์˜ switch๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

private func configureButton(frame: CGRect){
        barView = {
            let view = UIView(
                frame: CGRect(
                    x: 0,
                    y: 0,
                    width: frame.width,
                    height: frame.height
                )
            )
            view.backgroundColor = isOn ? onTintColor : offTintColor
            view.layer.cornerRadius = frame.height / 2
            view.layer.masksToBounds = true
            return view
        }()
        
        circleView = {
            let view = UIView(
                frame: CGRect(
                    x: isOn ? frame.width - (circleHorizontalMargin + (circleSize/2)) : circleHorizontalMargin + (circleSize/2),
                    y: circleVerticalMargin,
                    width: circleSize,
                    height: circleSize
                )
            )
            view.backgroundColor = ColorType.white01.color
            view.layer.cornerRadius = circleSize / 2
            view.layer.masksToBounds = true
            
            return view
        }()
        
        guard let circleView = circleView else { return }
        guard let barView = barView else { return }
        self.addSubviews(barView, circleView)
    }

 

 

touchesBegan

UISwitch๊ฐ€ ์•„๋‹Œ UIView๋ฅผ ์ปค์Šคํ…€ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— touch ์ด๋ฒคํŠธ ๋˜ํ•œ ๊ฐœ๋ฐœ์ž๊ฐ€ ์žก์•„์•ผ ํ–ˆ๋‹ค. UITapGestureRecognizer๋ฅผ ์‚ฌ์šฉํ• ๊นŒ๋„ ๊ณ ๋ฏผํ–ˆ์ง€๋งŒ, UITapGestureRecognizer๋Š” ํŠน์ •ํ•œ ๊ฐœ์ˆ˜์˜ ์†๊ฐ€๋ฝ ํ„ฐ์น˜๋ฅผ ์ธ์‹ํ•ด ์ข…๋ฅ˜ ๋ณ„๋กœ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” class๋ผ์„œ ์ปค์Šคํ…€ Switch์—์„œ๋Š” ๋‹จ์ˆœํ•˜๊ฒŒ ํ„ฐ์น˜ ์œ ๋ฌด์— ๋”ฐ๋ผ ์ž‘์—…์ด ์ด๋ฃจ์–ด์ง€๋Š” touchesBegan(_:with:)๊ฐ€ ๋” ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        isOn.toggle()
    }

 

 

Animation

Switct์˜ On/Off๋ฅผ ๋””์ž์ธ์ ์œผ๋กœ ๊ตฌ๋ถ„ํ•ด ์ฃผ๊ธฐ ์œ„ํ•ด isOn ํ”„๋กœํผํ‹ฐ๋ฅผ ์ƒ์„ฑํ•ด ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค bar์™€ ์›ํ˜• ๋ทฐ์˜ ์ƒ‰์ƒ์„ ๋ณ€๊ฒฝํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  switch๋Š” ์ƒ‰์ƒ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์›ํ˜•๋ทฐ์˜ ์›€์ง์ž„๋„ ์žˆ์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์• ๋‹ˆ๋ฉ”์ด์…˜๋„ ์ ์šฉํ•ด์•ผ ํ–ˆ๋‹ค.

var isOn: Bool = false {
        didSet {
            ...
        }
    }

์ƒ‰์ƒ ๋ณ€๊ฒฝ๊ณผ ์›ํ˜• ๋ทฐ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜๋„ ์ž˜ ์ง„ํ–‰๋˜๊ธฐ๋Š” ํ–ˆ์ง€๋งŒ ์›ํ˜• ๋ทฐ๊ฐ€ ๊ณ„์† ๋ฒ”์œ„ ๋ฐ–๊นŒ์ง€ ๋‚˜๊ฐ€๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค.

switch๋ฅผ ์•„์ง ๋ˆ„๋ฅด์ง€ ์•Š์€ ์ดˆ๊ธฐ ์ƒํƒœ์ผ๋•Œ๋Š” ์›ํ•˜๋Š” ์œ„์น˜์— ์žˆ์—ˆ์ง€๋งŒ, switch๋ฅผ ํด๋ฆญ ํ•˜๋ฉด ์ดˆ๊ธฐ ์ƒํƒœ์™€๋Š” ์›ํ˜• ๋ทฐ์˜ ์œ„์น˜๊ฐ€ ๋‹ฌ๋ž๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ• ๋‹นํ•˜๋Š” ๊ฐ’๋„ ๋ฐ”๊ฟ”๋ณด๊ณ , constaint๋„ ๋ฐ”๊ฟ”๋ณด๋Š” ๋“ฑ ์—ฌ๋Ÿฌ ๋…ธ๋ ฅ์„ ํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ์›ํ•˜๋Š” ์œ„์น˜์— ๋ฐฐ์น˜๋˜์ง€ ์•Š์•˜๋‹ค.

์˜ค๋ž˜ ๊ณ ๋ฏผํ•˜๋‹ค ๋†“์น˜๋Š” ๋ถ€๋ถ„์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์•„ ์ดˆ๊ธฐ ์ƒํƒœ์™€ ํด๋ฆญ ์ด๋ฒคํŠธ์—์„œ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค์— ์ง‘์ค‘ํ–ˆ๋‹ค. ์•Œ๊ณ ๋ณด๋‹ˆ ์ดˆ๊ธฐ ์„ค์ • ์‹œ์—๋Š” ์›ํ˜• ๋ทฐ ์ž์ฒด์˜ x์— ๊ฐ’์„ ํ• ๋‹นํ–ˆ๊ณ , switch ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ ํ›„์—๋Š” ์›ํ˜• ๋ทฐ์˜ center.x์— ๊ฐ’์„ ํ• ๋‹นํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

circleView = {
            let view = UIView(
                frame: CGRect(
                    x: isOn ? 
												frame.width - (circleHorizontalMargin + (circleSize/2)) 
												: circleHorizontalMargin + (circleSize/2),
                    ...
                )
            )
            ...
        }()
UIView.animate(
            withDuration: animationDuration,
            animations: { [weak self] in
                guard let self = self else { return }
                self.circleView?.center.x = self.isOn ? 
																						frame.width - (circleHorizontalMargin + (circleSize/2)) 
																						: circleHorizontalMargin + (circleSize/2)
            }
        )

์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋งŒ๋“ค ๋•Œ ์ฝ”๋“œ๋กœ x์™€ y ์ขŒํ‘œ๋ฅผ ํ• ๋‹นํ•ด์ฃผ๋Š”๋ฐ, ์–ด๋–ค ๊ฐ’์˜ x ๋˜๋Š” y ์ขŒํ‘œ์ธ์ง€ ์ž˜ ํŒŒ์•…ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ๋Š๋ผ๊ฒŒ ๋œ ๋ฌธ์ œ์˜€๋‹ค.

 

 

completion

switch On/Off์— ๋”ฐ๋ผ BottomSheet ํ™”๋ฉด ์ „ํ™˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ด์•ผํ–ˆ๋‹ค. ํŒŒ์ผ์— ๋ฐ”๋กœ ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๊ตฌํ˜„ํ•ด ๋‚ผ ์ˆ˜๋„ ์žˆ์—ˆ์ง€๋งŒ, ์ปค์Šคํ…€ ๋ทฐ๋Š” ์žฌ์‚ฌ์šฉ์„ฑ์ด ์ค‘์š”ํ•  ๊ฒƒ ๊ฐ™์•„ completion ํด๋กœ์ €๋ฅผ ์ƒ์„ฑํ•ด ์ด ์ปค์Šคํ…€ switch๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ๋งˆ๋‹ค ์›ํ•˜๋Š” ์ž‘์—…์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

var completion: ((_ isOn: Bool) -> Void) = { isOn in }

๊ธฐ๋Šฅ์„ ์‚ฌ์šฉ์žํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” DelegatePattern๋„ ์žˆ๋Š”๋ฐ ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” ์ปค์Šคํ…€์˜ ๊ทœ๋ชจ๊ฐ€ ํฌ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. Delegate๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Apple ํด๋ž˜์Šค๋ฅผ ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋Œ€ํ‘œ์ ์œผ๋กœ UICollectionView์™€ UITableView๊ฐ€ ์žˆ๋‹ค. ์ด ๋‘˜์€ Delegate๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์ •๋ง ๋งŽ๋‹ค. ํ•˜์ง€๋งŒ ๋‚ด๊ฐ€ ๋งŒ๋“  ์ปค์Šคํ…€ Switch๊ฐ™์€ ๊ฒฝ์šฐ ๋‹จ ํ•˜๋‚˜์˜ ํด๋กœ์ €๋งŒ ๊ตฌ์„ฑํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— Delegate ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค. ๋˜ ๋‹ค๋ฅธ ์ด์œ ๋กœ๋Š” Delegate ํŒจํ„ด์— ๋น„ํ•ด ํด๋กœ์ €๋Š” ์ง๊ด€์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ถ”ํ›„ ์žฌ์‚ฌ์šฉํ•  ๋•Œ ํŽธํ•  ๊ฒƒ ๊ฐ™์•˜๋‹ค.

 

728x90