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
- swift documentation
- HIG
- uiscrollview
- ios
- clean architecture
- map
- RxCocoa
- 클린 코드
- ribs
- SWIFT
- uitableview
- 리펙터링
- 스위프트
- 애니메이션
- Observable
- rxswift
- combine
- 리팩토링
- Xcode
- tableView
- Protocol
- swiftUI
- 리펙토링
- Clean Code
- MVVM
- UICollectionView
- Human interface guide
- collectionview
- Refactoring
- UITextView
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - Swift] Progress Button 만드는 방법 (카운트 다운 버튼) 본문
ProgressButton
- 특정 기능을 수행하고난 후 특정 시간내에 취소할 수 있는 카운트 다운을 시각적으로 보여주는 버튼 기능에 사용
구현 아이디어
- 원의 둘레는 CAShapeLayer와 CABasicAnimation을 통해 그리게끔 구현
- 애니메이션 끝난 경우 이벤트 수신은 CAAnimationDelegate를 통해 알림
- 안의 x 이미지는 UIImage 사용
구현
- 사용하는쪽
- ProgressButton을 초기화하고, progress를 돌리고 싶은 경우 animate(startRatio:) 사용
class ViewController: UIViewController {
private var progressButton: ProgressButton!
override func viewDidLoad() {
super.viewDidLoad()
progressButton = ProgressButton(radius: 50, completion: { [weak self] in
self?.progressButton.isHidden = true
})
progressButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(progressButton)
NSLayoutConstraint.activate([
self.progressButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
self.progressButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
self.progressButton.widthAnchor.constraint(equalToConstant: 100),
self.progressButton.heightAnchor.constraint(equalToConstant: 100),
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.progressButton.animate(startRatio: 0.0)
}
}
- ProgressButton 구현
- 필요한 property와 초기화 부분
- completion은 CABasicAnimation의 델리게이트인 CAAnimationDelegate의 animationDidStop에서 호출 (아래에서 계속)
final class ProgressButton: UIButton {
static let durationSeconds = 3.0
private let radius: CGFloat
private let color: UIColor
var completion: (() -> Void)?
init(
color: UIColor = .systemRed,
radius: CGFloat = 16.0,
completion: (() -> Void)? = nil
) {
self.radius = radius
self.color = color
self.completion = completion
super.init(frame: CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2))
backgroundColor = .clear
tintColor = color
imageView?.contentMode = .scaleAspectFit
let image = UIImage(systemName: "xmark")?.withRenderingMode(.alwaysTemplate)
setImage(image, for: .normal)
}
}
- 애니메이션 기능을 멈추는 cancel() 기능 구현
- sublayer들을 돌면서, animation을 삭제하고 layer를 지우는 메소드
func cancel() {
layer.sublayers?.forEach { layer in
if layer is CAShapeLayer {
layer.removeAllAnimations()
layer.removeFromSuperlayer()
}
}
}
- animate(startRatio:) 기능 구현
- CAShapeLayer와 UIBezierPath로 원 둘레 path 구하기
- "strokeEnd" 애니메이션 사용
func animate(startRatio: CGFloat) {
cancel()
// 1. 원 둘레 path 구하기
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
shapeLayer.lineWidth = 1.0
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: radius, y: radius),
radius: radius,
startAngle: 3 * .pi / 2,
endAngle: -.pi / 2,
clockwise: false
)
circlePath.lineWidth = 1
shapeLayer.path = circlePath.cgPath
// 2. 애니메이션 실행
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = startRatio
animation.toValue = 1.0
animation.duration = CFTimeInterval(Self.durationSeconds * (1 - startRatio))
animation.delegate = self
// 3. 원 둘레 path에다 animation 추가
shapeLayer.add(animation, forKey: "strokeEnd")
// 4. 현재 layer에 추가하여 적용
layer.addSublayer(shapeLayer)
}
- CAAnimationDelegate
- layer의 strokeColor에 색상 적용
- 애니메이션이 끝난 경우 completion 수행
extension ProgressButton: CAAnimationDelegate {
func animationDidStart(_ anim: CAAnimation) {
let layer = layer.sublayers?.last as? CAShapeLayer
layer?.strokeColor = color.cgColor
}
func animationDidStop(_ anim: CAAnimation, finished: Bool) {
guard finished else { return }
completion?()
}
}
'iOS 응용 (swift)' 카테고리의 다른 글
Comments