📑

【SwiftUI】年と月が選択できるホイールピッカーを自作した

2023/05/15に公開

概要

年と月を選択できるピッカーが欲しかったので、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