- 投稿日:2020-11-13T22:59:37+09:00
【Swift5】iOS14のUIDatePickerで日付を選択する
概要
DatePicker で日付選択をかっこよく、かつ楽に実装しようとしたところ
デザインがうまいこといかなかった際の試行錯誤を備忘録として残す。
ゴリ押し実装なので軽い気持ちでみてください。この記事の完成品
日付のラベル部分を押すとカレンダーから日付が選べる
選択が終わるとアニメーションしながら画面が閉じてラベル部分に日付が適応されるやりたかったこと
---年-月-日
と表示されている日付をタップするとiOS14のカレンダー式日付選択画面が表示される。
デザインはこんな感じなんかiOS14からDatePickerの挙動がかっこよくなったのでそれを使えば簡単だろうと思っていました。
iOS14のDatePicker関連の記事はこちら
https://qiita.com/kj_trsm/items/a53b0b3f7e1bc7c06106Try & Error
1:何も考えずUIButtonを置いてみた
ボタンを置いてデザインを合わせるも、いやいやDatePicker呼べないじゃんってなった(ColorPickerみたいに使うと思ったら違った)
カラーピッカーはこちら
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/204184/3ebb806c-59bf-6dd3-adb9-335026e7101d.jpeg2:DatePickerを置いてみる
上に書いた記事にもあるが、Styleがいくつかある
今回のデザインに近いであろうHoge.swiftdatePicker.preferredDatePickerStyle = .compact datePicker.datePickerMode = .date見た目はこんな感じ
挙動は希望通りなのであとはデザイン調整だ!っという気持ちになるが・・・
デザイン変更が上手いこといかない
- 背景色のグレーはなんだ? -->
datePicker.backgroundColor = .clear
にするも、変化なし- 文字を左揃えにしたい --> UILabel みたいな設定が見つからない
----年-月-日
という形式で表示させるのどうやるの?解決方法
背景色のグレーはなんだ?
Hoge.swiftdatePicker.subviews[0].subviews[0].subviews[0].backgroundColor = .clearで背景色が変えられた
文字を左揃えにしたい
-> 分からん、無理だった文字の左揃えができなかったので、ゴリ押しでUILabelの上にUIDatePickerを重ねることに
重ねた上でHoge.swiftdatePicker.subviews.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) })でdatePickerのSubViewsを消去することで謎のグレー背景も、日付の文字も消える。
重ねた下にあるUILabelの文字を、あたかもUIDatePickerの文字のように表示させる
そうすることでタップ時にはUIDatePickerが動作、表示する日付はUILabelとなり、簡単に希望通りの動作を作れた。
----年-月-日
という形式で表示させるのどうやるの? -> UILabelに表示させることにしたので調査してません完成形
StoryboardでUILabelとUIDatePickerを重ねて、UILabelに
---年-月-日
と設定
HogeViewController.swiftimport UIKit import RxSwift import RxCocoa class HogeViewController: UIViewController { @IBOutlet var datePicker: UIDatePicker! @IBOutlet var dateLabel: UILabel! private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() datePicker.rx.value.changed.asObservable() .subscribe({ event in if let date = event.element{ self.dateLabel.text = date.toDisplayString() } }) .disposed(by: disposeBag) } override func viewDidLayoutSubviews() { // datePicker を透明にする datePicker.subviews.forEach({ $0.subviews.forEach({ $0.removeFromSuperview() }) }) } }適当に
datePicker
の値の変更を監視してUILabelに日付として設定させれば完成
タップしたら値が設定され、閉じる時のアニメーションも勝手にDatePickerがやってくれたおわりに
結局重ねるというオチになってしまった。
きっともっとちゃんとした解決策がありそうだけど備忘録として残しておきます。
- 投稿日:2020-11-13T22:53:55+09:00
Swift初学者が電子レンジロジックを実装してみた!
概要
現在、私はMENTAサービスを利用し、ヤマタクメンターにご指導いただいています。
ヤマタクメンターやサービスについて知りたい方はこちら!
(https://menta.work/plan/584)その中で、今回は、応用学習の第2段ということで、
「電子レンジロジックの実装」の課題を行ったので、アウトプットしていきます。なお、今回の課題はSwift初学者が作ったロジックであり、至らぬ点が多々あると思います。どうか、温かい目で見守っていただけると幸いです。
アドバイス等ありましたら、ご教授いただけると幸いです。
環境
-Playground
実装の要件
・電子レンジのワット数は900,600,200とする
・今回は、安全のために11分以上は加熱できないものとする・タイマーの実装
・分と秒の概念を実装
・ 残り時間の出力
・残り時間が0秒になったら、「温め終了です」と出力し、タイマーを停止
・温める時間が11分以上だと「温めを開始できません!」、
「タイマーを10分59秒以内に設定してください」と出力・タイマーが10分59秒以内にセットされたら、温め可能フラグを返し、「温め開始!!」を出力
・選択されたワット数を出力実装
VirtualVendingMachine.rbimport UIKit import PlaygroundSupport class VirtualMicrowave : UIViewController { //タイマーをセットしてください var timeSet = (min:0,sec:0) //Timerクラスの初期化 var timer: Timer! //ワット数を決める enum powerConsumptionType{ case bottun900W case bottun600W case bottun200W var displayName: String{ switch self { case.bottun900W : return "900W" case.bottun600W : return "600W" case.bottun200W : return "200W" } } } override func viewDidLoad() { super.viewDidLoad() //スタートタイマーを呼び出す startTimer() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } //温め可能フラグを返す func startWarm(type: powerConsumptionType) -> Bool { var isWarmable = false if timeSet.min < 11 { print("温め開始!!:\(type.displayName)") return true } return isWarmable //カウントダウンタイマーを呼び出す countDownTimer() } @objc func countDownTimer(){ if timeSet.min == 0 && timeSet.sec == 0 { print("温め終了です") timer.invalidate() }else if timeSet.min > 10{ print("温めを開始できません!") print("タイマーを10分59秒以内に設定してください") timer.invalidate() }else if timeSet.sec > 0 && timeSet.min < 11 { //秒数が0以上の時または10分以下の時 timeSet.sec -= 1 print("\(timeSet.min)分\(timeSet.sec)秒") } else if (timeSet.sec == 0) && timeSet.min > 0 { //秒数が0の時 timeSet.sec += 59 timeSet.min -= 1 print("\(timeSet.min)分\(timeSet.sec)秒") } } //タイマーを起動するメソッド func startTimer() { timer = Timer.scheduledTimer( timeInterval: 1, target: self, selector: #selector(self.countDownTimer), userInfo: nil, repeats: true) } } let window = UIWindow(frame: CGRect(x: 0, y: 0, width: 300, height: 500)) let viewController = VirtualMicrowave() viewController.view.backgroundColor = UIColor.gray window.rootViewController = viewController window.makeKeyAndVisible() PlaygroundPage.current.liveView = window let virtualMicrowave = VirtualMicrowave() var isSuccessToWarm = virtualMicrowave.startWarm(type:.bottun200W) print(isSuccessToWarm)まとめ
今回は、引数の使い方やPlaygroundの環境ではあるが、タイマーメソッドの使い方を学んだ。
ゼロからロジックを考えたので、基礎構文の理解が深まった。
リファタリングを考えるとすれば、レンジ機能だけではなくオーブン、トースト機能などを追加してもいいかもしれません。
- 投稿日:2020-11-13T22:31:17+09:00
【Swift5】CustomViewをコードで実装する
はじめに
CustomView
をコードで実装した際に、init
メソッドに関しての理解が曖昧でしたので備忘録として投稿します。動作環境
【Xcode】Version 12.0.1
【Swift】Version 5.3実装コード
CustomView.swiftimport UIKit class CustomView: UIView { var imageView: UIImageView! // 詳細説明① override init(frame: CGRect) { super.init(frame: frame) setup() } // 詳細説明② required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } private func setup(){ let iWidth: CGFloat = 200 let iHeight: CGFloat = 200 let posX: CGFloat = (self.frame.width - iWidth)/2 let posY: CGFloat = (self.frame.height - iHeight)/2 imageView = UIImageView(frame: CGRect(x: posX, y: posY, width: iWidth, height: iHeight)) imageView.image = UIImage(named: "hoge") self.addSubview(imageView) } }ViewController.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let customView = CustomView() customView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) self.view.addSubview(customView) } }詳細説明①
override init(frame: CGRect) { super.init(frame: frame) }
- 通常のイニシャライザ(
required
が付かないイニシャライザ)はサブクラスで実装しても実装しなくても良いが、初期化などを行う場合はoverride
を必ず付ける必要があります。詳細説明②
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
- 必須のイニシャライザ(
required
が付いてるイニシャライザ)はサブクラスで絶対に実装しないといけません。init?(coder aDecoder: NSCoder)
のイニシャライザはNSCoding
プロトコルで定義されており、それがUIView
クラスで実装されているので、サブクラスでも実装する必要があるということになります。参考
- 投稿日:2020-11-13T22:31:17+09:00
CustomViewをコードで実装する
はじめに
CustomView
をコードで実装した際に、init
メソッドに関しての理解が曖昧でしたので備忘録として投稿します。動作環境
【Xcode】Version 12.0.1
【Swift】Version 5.3実装コード
CustomView.swiftimport UIKit class CustomView: UIView { var imageView: UIImageView! // 詳細説明① override init(frame: CGRect) { super.init(frame: frame) setup() } // 詳細説明② required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } private func setup(){ let iWidth: CGFloat = 200 let iHeight: CGFloat = 200 let posX: CGFloat = (self.frame.width - iWidth)/2 let posY: CGFloat = (self.frame.height - iHeight)/2 imageView = UIImageView(frame: CGRect(x: posX, y: posY, width: iWidth, height: iHeight)) imageView.image = UIImage(named: "hoge") self.addSubview(imageView) } }ViewController.swiftimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let customView = CustomView() customView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) self.view.addSubview(customView) } }詳細説明①
override init(frame: CGRect) { super.init(frame: frame) }
- 通常のイニシャライザ(
required
が付かないイニシャライザ)はサブクラスで実装しても実装しなくても良いが、初期化などを行う場合はoverride
を必ず付ける必要があります。詳細説明②
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
- 必須のイニシャライザ(
required
が付いてるイニシャライザ)はサブクラスで絶対に実装しないといけません。init?(coder aDecoder: NSCoder)
のイニシャライザはNSCoding
プロトコルで定義されており、それがUIView
クラスで実装されているので、サブクラスでも実装する必要があるということになります。参考
- 投稿日:2020-11-13T20:01:48+09:00
CIFilterのblurぼかしで画像を小さく縮ませない方法
画像が縮む問題
CIFilterのCIGaussianBlurでCIImageをぼかすと、画像がShrinkしてしまう。
let blurredImage = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputImageKey:originalCIImage, kCIInputRadiusKey:50])?.outputImage?CGImageを操作したり、AccelerateやMetalPerformanceShader、OpenCVのフィルターを使うと元と同じサイズのぼかし画像が得られるけれど、メモリ使用が大きい。
CoreImageのなかで完結すると、何枚ぼかしても数MBしかメモリを使わない。方法
let cropSize = CGRect(x: 0, y: 0, width: originalCIImage.extent.width * 0.999, height: originalCIImage.extent.height * 0.999) let blured = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputImageKey:originalCIImage, kCIInputRadiusKey:50])?.outputImage?.cropped(to: cropSize).resize(as: originalCIImage.extent.size)Cropすると画像サイズに合わせてくれます。
リサイズメソッドはこちら↓
extension CIImage { func resize(as size: CGSize) -> CIImage { let selfSize = extent.size let transform = CGAffineTransform(scaleX: size.width / selfSize.width, y: size.height / selfSize.height) return transformed(by: transform) } }?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-11-13T19:44:53+09:00
iOS13のPopover修正について
はじめに
今更ですが、iOS13からpopoverのmaskの重なり順が変更になっていたようで。。。
気付いたらpopoverで表示しているpickerなどが見切れていたので、
修正方法を備忘録としてまとめます。環境
swift5
Xcode 11.4.1修正前
UIのズレはこんな感じです。
左右に矢印がある時にはっきり見切れてしまいます。
ちなみに元々の実装はこんな感じにしてました。
hoge.swift// ImagePicker表示 let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.sourceType = .photoLibrary imagePicker.modalPresentationStyle = .popover imagePicker.mediaTypes = [kUTTypeMovie as String] let popover = imagePicker.popoverPresentationController popover?.sourceView = button popover?.sourceRect = button.bounds present(imagePicker, animated: true, completion: nil)修正方法
UIViewControllerのextensionを作成しました。
UIViewController+Picker.swiftextension UIViewController { /// Picker用のVCを作成し、Pickerを表示する(imagePicker用) /// /// - Parameters: /// - imagePickerController: UIImagePickerController /// - source: pickerの表示元となるView func presentPickerView(_ imagePickerController: UIImagePickerController, source: UIView) { guard let imagePickerView = imagePickerController.view else { return } // 土台となるVCを作成します let customPickerViewController = UIViewController() // まず先にaddChildします customPickerViewController.addChild(imagePickerController) // 土台のVCとimagePickerを同じ大きさにします imagePickerController.view.frame = customPickerViewController.view.frame // subViewになりうるviewにAutoLayoutをコード上で指定する場合はfalseにする imagePickerController.view.translatesAutoresizingMaskIntoConstraints = false // addSubviewします customPickerViewController.view.addSubview(imagePickerView) // ここで土台VCのsafaAreaに対してImagePickerの制約を指定をしていきます NSLayoutConstraint.activate([ imagePickerController.view.leadingAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.leadingAnchor), imagePickerController.view.trailingAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.trailingAnchor), imagePickerController.view.topAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.topAnchor), imagePickerController.view.bottomAnchor.constraint(equalTo: customPickerViewController.view.safeAreaLayoutGuide.bottomAnchor) ]) // didMoveでset完了 imagePickerController.didMove(toParent: customPickerViewController) // 以下はpickerの基本的な実装に戻ります。 // 土台のViewに大きさを指定(=pickerの大きさ) customPickerViewController.preferredContentSize = CGSize(width: 200, height: 200) customPickerViewController.modalPresentationStyle = .popover let popover = customPickerViewController.popoverPresentationController popover?.sourceView = source popover?.sourceRect = source.bounds present(customPickerViewController, animated: true, completion: nil) } }使う時はこんな感じ
ViewController.swift@IBOutlet func TapButton(_ sender: UIButton) { // 表示したいpickerを作成 let imagePicker = UIImagePickerController() // delegateとかは表示するclassで設定する imagePicker.delegate = self imagePicker.sourceType = .photoLibrary // extensionを使用して表示 presentPickerView(imagePicker, source: button) }おわり。
- 投稿日:2020-11-13T16:50:31+09:00
[Swift]キーボード上にあるツールバーの高さを動的に変更する方法
はじめに
UITextFieldやUITextViewで表示されるキーボード上のツールバーに、バー上のボタンをタップするともう1段バーが表示される方法になります。
イメージとしてはiOS純正のリマインダーアプリで実装されている、リマインド時間やロケールを選択するツールバーを同じものになります。
textView.inputAccessoryView = toolbar
でキーボード上にツールバーを設定する基本的な方法はよく見かけますが、
動的にバーの高さを変更する方法が見当たらなかったので参考になれば幸いです。完成イメージ
ツールバー上のベルのボタンをタップすると、もう1段上に通知時間のインターバルを選択するボタンが表示されます。
実装方法
はじめは
textView.inputAccessoryView = toolbar
で設定したtoolbarの高さを動的に変更しようとしたのですが、なかなかうまくできず。。。
(実装方法わかる方いれば教えてください!)
今回はtextView.inputAccessoryView = toolbar
で設定したtoolbarに対して子viewをaddSubview
、sendSubviewToBack
で裏側に隠しておいて、
上下に移動することで表示を切り替えています。今回はUITableViewにCustomTableViewCellを作成し、セル上にUITextViewを配置しています。
キーボード上にツールバーを表示するためにCustomTableViewCell内でUITextViewに対してキーボードが表示されるようにします。CustomTableViewCell.swiftlet lowerToolbar = LowerToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) lowerToolbar.initToolbarButton(item: item!) lowerToolbar.delegate = self textView.inputAccessoryView = lowerToolbar上記で設定しているlowerToolbarの中身がこちらになります
LowerToolbar.swiftprotocol LowerToolbarDelegete: class { func onTouchToolbarButton(selectedTimeInterval: Int) } class LowerToolbar: UIView { @IBOutlet weak var toolbar: UIToolbar! var upperToolbar: UIView? var isHidenUpperToolbar: Bool = true var upperToolbarCenterY: CGFloat = 0 weak var delegate: LowerToolbarDelegete! = nil let oneHourButton = UIButton(type: .system) let threeHourButton = UIButton(type: .system) let fiveHourButton = UIButton(type: .system) var item: ItemModel? var selectedTimeInterval: Int = 0 // Last tapped button. override init(frame: CGRect){ super.init(frame: frame) loadNib() } required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! loadNib() } func loadNib(){ let view = Bundle.main.loadNibNamed("LowerToolbar", owner: self, options: nil)?.first as! UIView view.frame = self.bounds self.addSubview(view) toolbar.clipsToBounds = true upperToolbar = UIView(frame: CGRect(x: 0, y: -1, width: self.frame.size.width, height: 61)) upperToolbar?.backgroundColor = UIColor.toolbar self.addSubview(upperToolbar!) self.sendSubviewToBack(upperToolbar!) upperToolbarCenterY = upperToolbar!.center.y } func createToolbarButton(btn: UIButton, title: String, timeInterval: Int) { btn.setTitle(title, for: .normal) btn.tag = timeInterval btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor.toolbarBorder.cgColor btn.layer.cornerRadius = 20 btn.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) btn.backgroundColor = UIColor.toolbarButton btn.addTarget(self, action: #selector(tapHoursButton), for: .touchUpInside) upperToolbar?.addSubview(btn) if self.item?.timeInterval == timeInterval { btn.layer.borderColor = UIColor.systemBlue.cgColor btn.backgroundColor = UIColor.systemBlue btn.tintColor = UIColor.white } } func initToolbarButton(item: ItemModel) { self.item = item createToolbarButton(btn: oneHourButton, title: "1hour", timeInterval: 60) oneHourButton.translatesAutoresizingMaskIntoConstraints = false oneHourButton.leadingAnchor.constraint(equalTo: upperToolbar!.leadingAnchor, constant: 20).isActive = true oneHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true createToolbarButton(btn: threeHourButton, title: "3hour", timeInterval: 180) threeHourButton.translatesAutoresizingMaskIntoConstraints = false threeHourButton.leadingAnchor.constraint(equalTo: oneHourButton.trailingAnchor, constant: 20).isActive = true threeHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true createToolbarButton(btn: fiveHourButton, title: "5hour", timeInterval: 400) fiveHourButton.translatesAutoresizingMaskIntoConstraints = false fiveHourButton.leadingAnchor.constraint(equalTo: threeHourButton.trailingAnchor, constant: 20).isActive = true fiveHourButton.topAnchor.constraint(equalTo: upperToolbar!.topAnchor, constant: 10).isActive = true } func initUpperToolbar() { upperToolbar?.center.y = upperToolbarCenterY isHidenUpperToolbar = true } func decorateTappedHourButton(btn: UIButton) { btn.layer.borderColor = UIColor.systemBlue.cgColor btn.backgroundColor = UIColor.systemBlue btn.tintColor = UIColor.white } func decorateNormalHourButton(btn: UIButton) { btn.layer.borderColor = UIColor.toolbarBorder.cgColor btn.backgroundColor = UIColor.toolbarButton btn.tintColor = .systemBlue } func decorateHourButton(btn1: UIButton, btn2: UIButton, btn3: UIButton, newTimeInterval: Int) { if selectedTimeInterval == newTimeInterval { selectedTimeInterval = 0 decorateNormalHourButton(btn: btn1) } else { selectedTimeInterval = newTimeInterval decorateTappedHourButton(btn: btn1) } decorateNormalHourButton(btn: btn2) decorateNormalHourButton(btn: btn3) } @objc func tapHoursButton(btn: UIButton) { switch btn.tag { case 60: decorateHourButton(btn1: oneHourButton, btn2: threeHourButton, btn3: fiveHourButton, newTimeInterval: btn.tag) case 180: decorateHourButton(btn1: threeHourButton, btn2: oneHourButton, btn3: fiveHourButton, newTimeInterval: btn.tag) case 400: decorateHourButton(btn1: fiveHourButton, btn2: oneHourButton, btn3: threeHourButton, newTimeInterval: btn.tag) default: print("no item") } delegate?.onTouchToolbarButton(selectedTimeInterval: btn.tag) } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if isHidenUpperToolbar { let rect = self.bounds return rect.contains(point) } else { var rect = self.bounds if rect.contains(point) { return rect.contains(point) } rect.origin.y -= 60 return rect.contains(point) } } @IBAction func tapBellButton(_ sender: Any) { if isHidenUpperToolbar { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y -= 60 self.isHidenUpperToolbar = false self.layoutIfNeeded() }) } else { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y += 60 self.isHidenUpperToolbar = true self.layoutIfNeeded() }) } } }解説
LowerToolbar.swiftfunc loadNib(){ let view = Bundle.main.loadNibNamed("LowerToolbar", owner: self, options: nil)?.first as! UIView view.frame = self.bounds self.addSubview(view) toolbar.clipsToBounds = true upperToolbar = UIView(frame: CGRect(x: 0, y: -1, width: self.frame.size.width, height: 61)) upperToolbar?.backgroundColor = UIColor.toolbar self.addSubview(upperToolbar!) self.sendSubviewToBack(upperToolbar!) upperToolbarCenterY = upperToolbar!.center.y }LowerToolbarを初期化する際に、UpperToolbarを作成し、
addSubview
してsendSubviewToBack
で背面に移動することで非表示にします。LowerToolbar.swift@IBAction func tapBellButton(_ sender: Any) { if isHidenUpperToolbar { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y -= 60 self.isHidenUpperToolbar = false self.layoutIfNeeded() }) } else { UIView.animate(withDuration: 0.1, animations: { self.upperToolbar!.center.y += 60 self.isHidenUpperToolbar = true self.layoutIfNeeded() }) } }ベルボタンをタップした際に、upperToolbarを上下に60移動しています。
LowerToolbar.swiftoverride func point(inside point: CGPoint, with event: UIEvent?) -> Bool { if isHidenUpperToolbar { // upperToolbarが非表示で // lowerToolbarがタップされた場合 let rect = self.bounds return rect.contains(point) } else { // upperToolbarが表示されているが // upperToolbarがタップされた場合 var rect = self.bounds if rect.contains(point) { return rect.contains(point) } // lowerToolbarがタップされた場合 rect.origin.y -= 60 return rect.contains(point) } }ポイントなのがこの部分で、upperToolbarを60上に移動した場合、親viewのlowerToolbarのframe領域からはみ出してしまうため、
upperToolbarの上に乗っているボタンのイベントを受け取れなくなってしまいます。
そこで、override func point(inside point: CGPoint, with event: UIEvent?) -> Bool
をオーバーライドして
upperToolbarがタップされた場合にイベントが受け取れるようにしています。その他気づき
今回はlowerToolbarの中でupperToolbarを生成してコードで作成しました。
これは、upperToolbarをstoryboardで個別に作成したものを使用したところ、
delegateを使用してタップされたイベントをupperToolbar->lowerToolbar->CustomTableViewCellへ移譲することができなかったためです。
(lowerToolbarでupperToolbarのdelegate設定できなかったです。)まとめ
ツールバーの高さを動的に変更できれば一番シンプルな実装にはなるかと思いますが、
高さの変更が思ったようにできず、違う方法でトライしてみました。
アップルの純正リマインダーアプリではどうやって実装してるんですかね。気になります。
何か参考になれば嬉しいです。
- 投稿日:2020-11-13T15:49:49+09:00
[Swift5]年月だけのDatePicker
年月だけのDatePickerを作ろう
初記事です!
textFieldをタップして年月だけのDatePickerを使用できるようにしたいと思い作成しました。
まだまだよくわかっていないことも多く
改善すべき点などありましたら、コメントいただけますと幸いです٩( 'ω' )و環境
・Swift 5.3
・Xcode 12.1ソースコード
1.MonthYearPickerViewクラスを作成
import UIKit class MonthYearPickerView: UIPickerView, UIPickerViewDelegate, UIPickerViewDataSource{ var months: [String]! var years: [Int]! var month = Calendar.current.component(.month, from: Date()) var year = Calendar.current.component(.year, from: Date()) var onDateSelected: ((_ month: Int, _ year: Int) -> Void)? override init(frame: CGRect) { super.init(frame: frame) self.commonInit() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.commonInit() } func commonInit() { var years: [Int] = [] if years.count == 0 { //5年前から表示する let previousDate = Calendar.current.date(byAdding: .year, value: -5, to: Date()) var year = NSCalendar(identifier: NSCalendar.Identifier.gregorian)!.component(.year, from: previousDate!) for _ in 1...10 { years.append(year) year += 1 } } self.years = years var months: [String] = [] var month = 0 for _ in 1...12 { var calendar = Calendar.current calendar.locale = Locale(identifier: "ja") let monthSymbols = calendar.monthSymbols months.append(monthSymbols[month]) month += 1 } self.months = months self.delegate = self self.dataSource = self //現在の年月を選択状態にする let currentYear = Calendar(identifier: Calendar.Identifier.gregorian).component(.year, from: Date()) let currentMonth = Calendar(identifier: Calendar.Identifier.gregorian).component(.month, from: Date()) self.selectRow(currentMonth-1, inComponent: 1, animated: false) if let i = years.firstIndex(where: {$0 == currentYear}){ self.selectRow(i, inComponent: 0, animated: false) } } //UIPickerDelegate&DataSource func numberOfComponents(in pickerView: UIPickerView) -> Int { return 2 } func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { switch component { case 0: return "\(years[row])年" case 1: return "\(months[row])分" default: return nil } } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { switch component { case 0: return years.count case 1: return months.count default: return 0 } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let year = years[self.selectedRow(inComponent: 0)] let month = self.selectedRow(inComponent: 1)+1 self.month = month self.year = year if let block = onDateSelected { block(month, year) } } }2.ViewControllerのViewDidLoad()でtextFieldのinputViewに設定する
let datePickerView = MonthYearPickerView() datePickerView.onDateSelected = { [self] (month: Int, year: Int) in let dateText = String(format: "%d年%02d月分",year,month) self.dateTextField.text = dateText } self.dateTextField.inputView = datePickerView self.dateTextField.delegate = self //textFieldタップで現在の年月をtextFieldに反映させたかったので以下を記述 extension ViewController: UITextFieldDelegate{ func textFieldDidBeginEditing(_ textField: UITextField) { if textField == dateTextField { let formatter = DateFormatter() formatter.dateFormat = "yyyy年MM月分" dateTextField.text = "\(formatter.string(from: Date()))" } } }できたー!! ٩( 'ω' )و
こんな感じでtextFieldをタップした時にDatePickerが表示されるようになりました。
めちゃめちゃ参考にさせていただいたサイト
https://github.com/bendodson/MonthYearPickerView-Swift
参考というかもはやコピペですね。。。。
ありがとうございました
- 投稿日:2020-11-13T00:24:47+09:00
TextFieldと連携したDatePickerを二つ作る
はじめに
UIDatePickerを2つ作り、TextFieldにインプットする方法をまとめてみます。
開発環境
macOS Catalina 10.15.7
Xcode 12.1
Swift 5.3テキストフィールドの設置と変数の準備
まず、テキストフィールド2つをStoryBoard上で任意の場所に設置します。
今回は1つ目のテキストフィールドをATextField、2つ目をBTextFieldとしました。また、以下の2つの変数を用意します。
ViewController.swiftclass ViewController: UIViewController { @IBOutlet weak var ATextField: UITextField! @IBOutlet weak var BTextFiled: UITextField! var APicker: UIDatePicker! var BPicker: UIDatePicker! override func viewDidLoad() { super.viewDidLoad() } }DatePickerを作成するメソッドの定義
次に、TextFieldに入れるDatePickerを作成するメソッドを定義します。
ViewController.swiftfunc makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels return myPicker }作成するDatePickerは2つあるので、引数にtrueかfalseをとる形にし、trueであればATextFieldに挿入するDatePickerが、falseであればBTextFiledに挿入するDatePickerが作成されます。
それぞれのDatePickerにタグを1、2とふっておきます。
DatePickerの設定についてはこのメソッド内で自由に設定します。iOS14でのDatePickerの挙動についてはこちらの記事を参考にしました。
→https://qiita.com/kj_trsm/items/a53b0b3f7e1bc7c06106DatePickerの変更を検知するメソッドの定義
DatePickerがユーザーによって操作された際に入力された日付をテキストフィールドに表示するメソッドを定義します。
ViewController.swift@objc internal func dateChanged(sender: UIDatePicker){ let formatter: DateFormatter = DateFormatter() formatter.dateFormat = "yyyy年M月d日" let selectedDate = formatter.string(from: sender.date) if sender.tag == 1 { ATextField.text = selectedDate } else { BTextFiled.text = selectedDate } }まずDate型をString型に変換するDateFormatterを用意し、変換した文字列をselectedDate変数に格納した後、senderのタグに従って入力先のテキストフィールドに文字列を代入します。
ここまでできたら、先ほど定義したmakePickerメソッド内で、myPickerにaddTargetします。
ViewController.swiftfunc makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels //ここ myPicker.addTarget(self, action: #selector(onDidChangeDate(sender:)), for: .valueChanged) return myPicker }キーボードを引っ込ませる処理
このままだと、DatePickerの操作を始めるとキーボードが出っ放しになるので、
キーボード外をタップするとキーボードが引っ込む処理を行います。ViewController.swiftoverride func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ATextField.endEditing(true) BTextFiled.endEditing(true) }ViewDidLoad内でDatePickerを作成
最後に、ViewDidLoad内でDatePickerを作成するメソッドを呼び、テキストフィールドにインプットして完成です。
ViewContoller.swiftoverride func viewDidLoad() { super.viewDidLoad() APicker = makePicker(true) ATextField.inputView = APicker BPicker = makePicker(false) BTextFiled.inputView = BPicker }コード全体
ViewCOntroller.swiftclass ViewController: UIViewController { @IBOutlet weak var ATextField: UITextField! @IBOutlet weak var BTextFiled: UITextField! var APicker: UIDatePicker! var BPicker: UIDatePicker! override func viewDidLoad() { super.viewDidLoad() APicker = makePicker(true) ATextField.inputView = APicker BPicker = makePicker(false) BTextFiled.inputView = BPicker } func makePicker(_ isA:Bool) -> UIDatePicker { let myPicker:UIDatePicker! myPicker = UIDatePicker() myPicker.tag = isA ? 1 : 2 myPicker.datePickerMode = .date myPicker.locale = NSLocale(localeIdentifier: "ja_JP") as Locale myPicker.preferredDatePickerStyle = .wheels myPicker.addTarget(self, action: #selector(onDidChangeDate(sender:)), for: .valueChanged) return myPicker } @objc internal func onDidChangeDate(sender: UIDatePicker){ let formatter: DateFormatter = DateFormatter() formatter.dateFormat = "yyyy年M月d日" let mySelectedDate = formatter.string(from: sender.date) if sender.tag == 1 { ATextField.text = mySelectedDate } else { BTextFiled.text = mySelectedDate } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { ATextField.endEditing(true) BTextFiled.endEditing(true) } }