カテゴリー: iOS

月と日を選択するpickerを作る

はじめに

日付を選択するピッカーが必要になった場合、iOSアプリではUIDatePickerクラスを使えば簡単に作ることができますが、
反面、表示を細かくカスタマイズしたい場合に融通がきかないところがあります。

今回、月と日にちのみを選択するピッカーが必要になったので、UIPickerViewクラスのカスタムクラスを実装して対応しました。

前提条件

  • Xcode 8.3.3
  • Swift version 3.1

 

サンプル

ピッカーのイメージ

実装したピッカーのイメージは下記の通りです。

ピッカーのxibファイル

ピッカーのxibファイルの内容は下記画像の通りです。
UIButtonの下にUIPickerViewを配置しています。

実装したカスタムクラス

今回作成したカスタムピッカーのコードは下記の通りです。

import UIKit

class MonthAndDayPickerView: UIView, UIPickerViewDelegate, UIPickerViewDataSource {

    @IBOutlet weak var doneButton: UIButton!
    @IBOutlet weak var pickerView: UIPickerView!
    
    let dayOfMonth = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    var monthIndex = 0
    
    init(bounds: CGRect) {
        let baseWidth = 375.0
        let baseHeight = 260.0
        let viewRate = Double(bounds.width) / baseWidth
        let pickerRect = CGRect(x: 0, y: 0, width: baseWidth * viewRate, height: baseHeight * viewRate)
        super.init(frame: pickerRect)
        commonInit()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    func getMonthAndDay() -> String {
        let month = "\(pickerView.selectedRow(inComponent: 0) + 1)月"
        let day = "\(pickerView.selectedRow(inComponent: 1) + 1)日"
        return month + day
    }
    
    private func commonInit() {
        let view = Bundle.main.loadNibNamed("MonthAndDayPicker", owner: self, options: nil)?.first as! UIView
        view.frame = self.bounds
        view.translatesAutoresizingMaskIntoConstraints = true
        view.autoresizingMask = .flexibleWidth
        self.addSubview(view)
        
        // Pickerの設定
        pickerView.delegate = self
    }
    
    // MARK: - UIPickerView data source
    
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 2
    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch component {
        case 0:
            return dayOfMonth.count
        case 1:
            return dayOfMonth[monthIndex]
        default:
            return 0
        }
    }
    
    // MARK: - UIPickerView delegate
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        if component == 0 {
            return "\(row + 1)月"
        } else if component == 1 {
            return "\(row + 1)日"
        } else {
            return nil
        }
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        if component == 0 {
            // 選択している月が変わった場合最終日が変わるので、日を再生成する
            monthIndex = row
            pickerView.reloadComponent(1)
        }
    }
}

呼び出し元のコード

呼び出し元のコードは下記の通りです。

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    let defaultBirthday = "未設定"
    var monthAndDayPickerView:MonthAndDayPickerView!
    
    @IBOutlet weak var birthdayField: UITextField!
    
    func setDatePicker() {
        monthAndDayPickerView = MonthAndDayPickerView(bounds: self.view.bounds)
        birthdayField.inputView = monthAndDayPickerView
        monthAndDayPickerView.doneButton.addTarget(
            self,
            action: #selector(ViewController.datePickerSelected),
            for: UIControlEvents.touchUpInside)
    }
    
    func datePickerSelected() {
        birthdayField.text =  monthAndDayPickerView.getMonthAndDay()
        view.endEditing(true)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        birthdayField.text = defaultBirthday
        birthdayField.delegate = self
        
        // DatePickerの生成
        setDatePicker()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

 

簡単な解説

MonthAndDayPickerView.swift

3行目:UIPickerViewに関するプロトコルを採用します。

8行目:月ごとに最大日数が異なりますので、1月から12までの各月の日数を定義します。

49〜51行目:ピッカーのコンポーネント数を返します(月と日にちを選択できるので2を返す)

53〜62行目:各コンポーネントの行数を返します。ピッカーが生成されたタイミングとコンポーネントがリロードされたタイミングで実行されます。

66〜74行目:コンポーネントに表示する文字列を返します。

76〜82行目:コンポーネントの値が変化したタイミングで実行されます。月が変更されたタイミングで日にちのコンポーネントをリロードします。

ViewController.swift

12行目:UITextFieldのinputViewにカスタムピッカービューを代入しています。これにより、テキストフィールドをタップした時にピッカーが表示されるようになります。

13〜16行目:カスタムピッカーのDoneボタンにアクションを追加しています。これにより、Doneボタンがタップされた時にdatePickerSelectedメソッドが実行されるようになります。

19〜22行目:Doneボタンがタップされた時にテキストフィールドを更新してピッカーを非表示にします。
 

さいごに

月と日にちを選択できるUIPickerViewのカスタムクラスを実装しました。

iOSアプリ開発はObjective-Cのシンタックスに違和感を感じることから敬遠していましたが、Swiftのシンタックスはかなり書きやすいと思います。
以前Objective-Cで開発していた方は、この機会にSwiftを初めてみてはいかがでしょうか。

Hiroki Ono

シェア
執筆者:
Hiroki Ono

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前