Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- HIG
- Protocol
- uiscrollview
- combine
- swiftUI
- 리펙터링
- rxswift
- 클린 코드
- map
- Xcode
- ios
- Refactoring
- 리팩토링
- 스위프트
- 리펙토링
- Observable
- UITextView
- SWIFT
- Clean Code
- tableView
- uitableview
- UICollectionView
- swift documentation
- MVVM
- 애니메이션
- collectionview
- RxCocoa
- clean architecture
- ribs
- Human interface guide
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Custom Switch (커스텀 스위치) 구현 방법, touchesEnded 본문
UI 컴포넌트 (swift)
[iOS - swift] Custom Switch (커스텀 스위치) 구현 방법, touchesEnded
jake-kim 2022. 6. 2. 23:52
사용하는 쪽
class ViewController: UIViewController {
private let jkSwitchOne: JKSwitch = {
let view = JKSwitch()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let jkSwitchTwo: JKSwitch = {
let view = JKSwitch()
view.barTintColor = .red
view.circleColor = .orange
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.jkSwitchOne)
self.view.addSubview(self.jkSwitchTwo)
NSLayoutConstraint.activate([
self.jkSwitchOne.heightAnchor.constraint(equalToConstant: 70),
self.jkSwitchOne.widthAnchor.constraint(equalToConstant: 200),
self.jkSwitchOne.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 150),
self.jkSwitchOne.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
])
NSLayoutConstraint.activate([
self.jkSwitchTwo.heightAnchor.constraint(equalToConstant: 50),
self.jkSwitchTwo.widthAnchor.constraint(equalToConstant: 100),
self.jkSwitchTwo.topAnchor.constraint(equalTo: self.jkSwitchOne.bottomAnchor, constant: 80),
self.jkSwitchTwo.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
])
}
}
구현 아이디어
- 동그라미 뷰(circleView)와 배경 뷰(barView)를 정의
- barView안에 circleView를 넣고 circleView의 left, top, bottom을 barView와 같도록 레이아웃 초기화
- 배경 뷰와 동그라미 뷰 모두 cornerRadius값을 height의 반으로 설정하여 round 뷰 형태로 유지
- roundableView 만드는 방법은 이전 포스팅 글 참고
- 터치가 발생할때마다 동그란 circleView를 오토레이아웃을 이용하여 오른쪽 or 왼쪽으로 이동하도록 구현
- 터치가 발생한 이벤트는 touchesBegan() 메소드를 통해 알수 있고, 이 메소드가 불릴때 레이아웃 업데이트
구현
- circleView와 barView 둘다 roundableView 형태이므로 RoundableView 정의
- roundableView 관련 개념은 이전 포스팅 글 참고
final class RoundableView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.frame.height / 2
}
}
- JKSwitch 클래스 준비
- UIControl를 서브클래싱하여 사용
import UIKit
final class JKSwitch: UIControl {
}
// UIControl를 서브클래싱한 이유
// JKSwitch에서 switch값이 바뀌는 부분에 아래 코드를 넣으면 사용하는쪽에서 addTarget으로 값이 변경될때 이벤트처리가 가능
self.sendActions(for: .valueChanged)
// 사용하는쪽
view.addTarget(self, action: #selector(changeSwitchValue), for: .valueChanged)
- UI 2개와 오토레이아웃 변경시 duration을 상수로 준비
- circleView에는 입체감을 주기 위해서 shadow 처리도 추가
private enum Constant {
static let duration = 0.25
}
// MARK: UI
private let barView: RoundableView = {
let view = RoundableView()
view.backgroundColor = .gray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let circleView: RoundableView = {
let view = RoundableView()
view.backgroundColor = .white
view.layer.shadowOffset = CGSize(width: 0, height: 3)
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.3
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
- 외부에서 접근할수 있는 프로퍼티 준비
var barColor = UIColor.gray {
didSet { self.barView.backgroundColor = self.barColor }
}
var barTintColor = UIColor.green
var circleColor = UIColor.white {
didSet { self.circleView.backgroundColor = self.circleColor }
}
- 스윗칭될때마다 circleView의 오토레이아웃을 변경해주어야 하므로 isActive를 false로 시키고 다시 정의하기 위해서 프로퍼티로 준비
private var circleViewConstraints = [NSLayoutConstraint]()
- 외부에서 스윗칭 할 수 있는 isOn 프로퍼티 선언
- self.sendActions(for: .valueChanged) 코드를 통해 사용하는쪽에서 valueChanged 이벤트 처리가 가능하게끔 정의
- 이곳에서 스위치 상태에 따라 배경색과 autolayout을 애니메이션을 통해 변경
var isOn = false {
didSet {
self.sendActions(for: .valueChanged)
UIView.animate(
withDuration: Constant.duration,
delay: 0,
options: .curveEaseInOut,
animations: {
self.barView.backgroundColor = self.isOn ? self.barTintColor : self.barColor
self.circleViewConstraints.forEach { $0.isActive = false }
self.circleViewConstraints.removeAll()
if self.isOn {
self.circleViewConstraints = [
self.circleView.rightAnchor.constraint(equalTo: self.barView.rightAnchor),
self.circleView.bottomAnchor.constraint(equalTo: self.barView.bottomAnchor),
self.circleView.topAnchor.constraint(equalTo: self.barView.topAnchor),
self.circleView.heightAnchor.constraint(equalTo: self.barView.heightAnchor),
self.circleView.widthAnchor.constraint(equalTo: self.barView.heightAnchor)
]
} else {
self.circleViewConstraints = [
self.circleView.leftAnchor.constraint(equalTo: self.barView.leftAnchor),
self.circleView.bottomAnchor.constraint(equalTo: self.barView.bottomAnchor),
self.circleView.topAnchor.constraint(equalTo: self.barView.topAnchor),
self.circleView.heightAnchor.constraint(equalTo: self.barView.heightAnchor),
self.circleView.widthAnchor.constraint(equalTo: self.barView.heightAnchor)
]
}
NSLayoutConstraint.activate(self.circleViewConstraints)
self.layoutIfNeeded()
},
completion: nil
)
}
}
- 레이아웃 초기화
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.barView)
self.barView.addSubview(self.circleView)
NSLayoutConstraint.activate([
self.barView.leftAnchor.constraint(equalTo: self.leftAnchor),
self.barView.rightAnchor.constraint(equalTo: self.rightAnchor),
self.barView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
self.barView.topAnchor.constraint(equalTo: self.topAnchor),
])
self.circleViewConstraints = [
self.circleView.leftAnchor.constraint(equalTo: self.barView.leftAnchor),
self.circleView.bottomAnchor.constraint(equalTo: self.barView.bottomAnchor),
self.circleView.topAnchor.constraint(equalTo: self.barView.topAnchor),
self.circleView.heightAnchor.constraint(equalTo: self.barView.heightAnchor),
self.circleView.widthAnchor.constraint(equalTo: self.barView.heightAnchor)
]
NSLayoutConstraint.activate(self.circleViewConstraints)
}
- 터치가 끝난 경우, isOn값을 변경해주는 코드를 touchesEnded에 적용
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
self.isOn = !self.isOn
}
* 전체 코드: https://github.com/JK0369/ExSwitch
'UI 컴포넌트 (swift)' 카테고리의 다른 글
Comments