본문 바로가기
iOS📱/SwiftUI

[SwiftUI] 커스텀 세로 슬라이더 ( Custom Vertical Slider )

by 텅빈비니 2024. 6. 14.
반응형

안녕하세요🐶

빈지식 채우기의 비니🙋🏻‍♂️ 입니다.

 

오늘은 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)

            }

        }

    }

}


감사합니다.


참고

반응형