📑
【SwiftUI】年と月が選択できるホイールピッカーを自作した
概要
年と月を選択できるピッカーが欲しかったので、SwiftUIでUIViewRepresentableを使って、マルチホイールピッカーを自作しました。
動作環境
XCode14.1
iOS15.0
完成例
・年は1900〜2100年まで対応(範囲変更可)
・月は擬似的な無限ホイール
コード
以下のファイルを追加して下さい。
import SwiftUI
import UIKit
struct CustomDatePicker: UIViewRepresentable {
@Binding var selectedYear: Int
@Binding var selectedMonth: Int
let years: [Int] = Array(1900...2100)
let months: [Int] = Array(repeating: Array(1...12), count: 100).flatMap { $0 }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIPickerView {
let pickerView = UIPickerView()
pickerView.delegate = context.coordinator
pickerView.dataSource = context.coordinator
if let yearRow = years.firstIndex(of: selectedYear), let monthRow = months.firstIndex(of: selectedMonth) {
pickerView.selectRow(yearRow, inComponent: 0, animated: false)
// 初期位置を中央に設定することで、ループをシミュレート
pickerView.selectRow(monthRow + 12 * 49, inComponent: 1, animated: false)
}
return pickerView
}
func updateUIView(_ uiView: UIPickerView, context: Context) {
uiView.reloadAllComponents()
}
class Coordinator: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
var parent: CustomDatePicker
init(_ parent: CustomDatePicker) {
self.parent = parent
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return parent.years.count
} else {
return parent.months.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return "\(parent.years[row])年"
} else {
return "\(parent.months[row])月"
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
parent.selectedYear = parent.years[row]
} else {
parent.selectedMonth = parent.months[row]
}
}
}
}
使用したいviewにこんな感じで追加
struct TestView: View {
@State private var pickerIsPresented = false
@State var diff: Int = 0
//年月ピッカー用
@State private var yearSelection: Int = {
let currentDate = Date()
let calendar = Calendar.current
return calendar.component(.year, from: currentDate)
}()///現在の年が初期値
@State private var monthSelection: Int = {
let currentDate = Date()
let calendar = Calendar.current
return calendar.component(.month, from: currentDate)
}()///現在の月が初期値
func pickerToDiff() -> Int {///ピッカーで指定した年月から、現在の
let monthNum:Int = monthSelection
let yearDiff:Int = yearSelection - Calendar.current.dateComponents([.year], from: Date()).year!
let monthDiff:Int = monthNum - Calendar.current.dateComponents([.month], from: Date()).month!
diff = yearDiff * 12 + monthDiff
}
var body: some View {
VStack {
Button(action: {
if pickerIsPresented {
pickerToDiff()
}
pickerIsPresented = !pickerIsPresented
}){Text(Date().changeMonth(diff: diff).toString(format: "yyyy年M月"))
.foregroundColor(Color.black)
if pickerIsPresented {
Image(systemName: "chevron.down")
}else{
Image(systemName:"chevron.forward")
}
}
if pickerIsPresented {
CustomDatePicker(selectedYear: $yearSelection, selectedMonth: $monthSelection)
}
}
}
}
解説
変数diffに、現在の年と月から何ヶ月差があるかを保存しています。
この例では不要ですが、色々と利用する際に便利です。
Discussion