안녕하세요🐶
빈지식 채우기의 비니🙋🏻♂️ 입니다.
오늘은 SwiftUI 를 통해 세로 슬라이더를 만들어보도록 하겠습니다.
위와 같이 구현을 해볼게요!
1. 구현
import Foundation
import SwiftUI
struct VolumeView: View {
@State var sliderColor: UIColor // Slider 색상
@State var maxHeight: CGFloat // Slider 최대 길이
@State var sliderHeight: CGFloat // Slider 채워진 길이
@State var sliderProgress: CGFloat // Slider 채워진 퍼센트
@State var lastDragValue: CGFloat // Slider 마지막 드래그 위치
let sliderWidth: CGFloat = 40.0 // Slider 너비
...
}
- @State 를 사용하여 사용될 변수를 선언합니다.
- 해당 값에 의존하는 뷰가 다시 랜더링되어 화면이 업데이트가 되도록 합니다.
/// Init 함수
/// - Parameters:
/// - maxHeight: 슬라이더 길이
/// - progress: 슬라이더 초기상태
/// - color: 슬라이더 색상
/// - Returns: 없음
init(maxHeight: CGFloat, progress: CGFloat, color: UIColor) {
self.maxHeight = maxHeight
self.sliderColor = color
self.sliderHeight = maxHeight * progress
self.sliderProgress = progress
self.lastDragValue = maxHeight * progress
}
- 초기화 함수를 구현합니다.
var body: some View {
ZStack(alignment: .bottom, content: {
Rectangle() // 채워지지 않은 Bar
.fill(Color(.white))
Rectangle() // 채워지는 Bar
.fill(Color(sliderColor))
.frame(height: sliderHeight)
.clipShape(RoundedRectangle(cornerRadius: 40)) // 둥글게 구현
})
.frame(width: sliderWidth, height: maxHeight)
.clipShape(RoundedRectangle(cornerRadius: 50)) // 둥글게 구현
...
}
- ZStack 을 사용하여 세로 템플릿을 사용합니다.
- 2개의 Rectangle() 을 사용하여 채워지는 부분과 채워지지 않는 부분을 생성합니다.
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ value in
let translation = value.translation
// 현재 채워진 길이 계산
sliderHeight = -translation.height + lastDragValue
// 현재 채워진 길이 계산 ( 예외처리 )
sliderHeight = sliderHeight > maxHeight ? maxHeight : sliderHeight
sliderHeight = sliderHeight >= 0 ? sliderHeight : 0
// 현재 채워진 퍼센트 계산
sliderProgress = sliderHeight / maxHeight <= 1.0 ? sliderHeight / maxHeight : 1.0
})
.onEnded({ value in
// 현재 채워진 길이 계산 ( 예외처리 )
sliderHeight = sliderHeight > maxHeight ? maxHeight : sliderHeight
sliderHeight = sliderHeight >= 0 ? sliderHeight : 0
// 마지막 드래그 위치 계산
lastDragValue = sliderHeight
})
)
- 사용자 제스쳐(Interaction)을 구현합니다.
- 예외 처리를 통해 Progress 가 0 이하 또는 1 이상으로 가지 않게 구현합니다.
.overlay(alignment: .bottom) { // 이미지 삽입
if sliderProgress == 0 { // 현재 채워진 퍼센트가 0이면?
Image("volume_off") // 볼륨 Off
.resizable()
.scaledToFit()
.frame(width: 45)
} else {
Image("volume") // 볼륨 On
.resizable()
.scaledToFit()
.frame(width: 27)
.padding(.vertical, 15)
}
}
- Slider 위에 삽입될 이미지를 구현합니다.
- Progress 값을 통해 볼륨 On, Off 를 삽입합니다.
import UIKit
import Foundation
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// SwiftUI 뷰를 UIKit으로 변환 뒤 사용
let volumeVC = UIHostingController(rootView: VolumeView(maxHeight: UIScreen.main.bounds.height / 3.5,
progress: 0.6,
color: UIColor(red: 66 / 255, green: 151 / 255, blue: 255 / 255, alpha: 0.6)))
volumeVC.view.frame = .init(x: 100, y: 100, width: 40, height: UIScreen.main.bounds.height / 3.5)
volumeVC.view.layer.cornerRadius = 50
self.view.addSubview(volumeVC.view)
}
}
- 해당 SwiftUI 뷰를 ViewController(UIKit) 에 호출하기 위해서는 위와 같이 UIHostingController가 필요합니다.
정상적으로 작동이 되는 모습을 확인할 수 있습니다!
전체소스 첨부 드립니다.
import Foundation
import SwiftUI
struct VolumeView: View {
@State var sliderColor: UIColor // Slider 색상
@State var maxHeight: CGFloat // Slider 최대 길이
@State var sliderHeight: CGFloat // Slider 채워진 길이
@State var sliderProgress: CGFloat // Slider 채워진 퍼센트
@State var lastDragValue: CGFloat // Slider 마지막 드래그 위치
let sliderWidth: CGFloat = 40.0
/// Init 함수
/// - Parameters:
/// - maxHeight: 슬라이더 길이
/// - progress: 슬라이더 초기상태
/// - color: 슬라이더 색상
/// - Returns: 없음
init(maxHeight: CGFloat, progress: CGFloat, color: UIColor) {
self.maxHeight = maxHeight
self.sliderColor = color
self.sliderHeight = maxHeight * progress
self.sliderProgress = progress
self.lastDragValue = maxHeight * progress
}
var body: some View {
ZStack(alignment: .bottom, content: {
Rectangle()
.fill(Color(.white))
Rectangle()
.fill(Color(sliderColor))
.frame(height: sliderHeight)
.clipShape(RoundedRectangle(cornerRadius: 40))
})
.frame(width: sliderWidth, height: maxHeight)
.clipShape(RoundedRectangle(cornerRadius: 50))
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ value in
let translation = value.translation
// 현재 채워진 길이 계산
sliderHeight = -translation.height + lastDragValue
// 현재 채워진 길이 계산 ( 예외처리 )
sliderHeight = sliderHeight > maxHeight ? maxHeight : sliderHeight
sliderHeight = sliderHeight >= 0 ? sliderHeight : 0
// 현재 채워진 퍼센트 계산
sliderProgress = sliderHeight / maxHeight <= 1.0 ? sliderHeight / maxHeight : 1.0
})
.onEnded({ value in
// 현재 채워진 길이 계산 ( 예외처리 )
sliderHeight = sliderHeight > maxHeight ? maxHeight : sliderHeight
sliderHeight = sliderHeight >= 0 ? sliderHeight : 0
// 마지막 드래그 위치 계산
lastDragValue = sliderHeight
})
)
.overlay(alignment: .bottom) { // 이미지 삽입
if sliderProgress == 0 { // 현재 채워진 퍼센트가 0이면?
Image("volume_off") // 볼륨 Off
.resizable()
.scaledToFit()
.frame(width: 45)
} else {
Image("volume") // 볼륨 On
.resizable()
.scaledToFit()
.frame(width: 27)
.padding(.vertical, 15)
}
}
}
}
감사합니다.