どもども。
UITableViewのセルを選択すると、UIPickerViewを表示して、その値をセル上のラベルに表示するというのを実装してみたので紹介します。
こんな感じのです。
なんだかんだで結構実装に時間かかりました・・・。
大雑把にですが紹介していきます。
色々バラけて読みづらいので全体コードから見た方が良いかもです(´・ω・`)
TableViewを設置しよう
UITableViewを設置します。
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource{
//テーブルビュー
private var tableView:UITableView!
//テーブルビューのテキストラベル
private let textArray = ["性別","好きな動物","好きなスポーツ"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let width = self.view.frame.width
let height = self.view.frame.height
//tableView
tableView = UITableView(frame: CGRect(x:0,y:50,width:width,height:height - 50))
tableView.delegate = self
tableView.dataSource = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
}
//セルの数
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray.count
}
//セルの設定
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.textLabel?.text = textArray[indexPath.row]
return cell
}
//セルを選択した時
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//後から書くよ
}
UITableViewの設置自体の細かい説明は割愛します(´・ω・`)
カスタムセルを作ろう
File > New > File より
Cocoa Touch Class > Next
Class:CustomTableViewCell Subclass of : UITableViewCell
で新しいファイルを作成します。
セルの右側にPickerViewで選択した値を表示するラベルを設置します。
CustomTableViewCellには以下のように記述しました。
import UIKit
class CustomTableViewCell: UITableViewCell {
public var rightLabel:UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
rightLabel = UILabel()
rightLabel.frame = CGRect(x:self.frame.width / 2,y:0,width:self.frame.width / 2,height:self.frame.height)
rightLabel.textAlignment = .center
//セルのアクセサリービューにラベルを設定
self.accessoryView = rightLabel
}
}
addSubviewでもいいんですが、accessoryViewのが簡単ぽい。
チェックマークとかもここに設定するらしいです。
結構悩んだところなんですが、UITableViewCellを継承したクラスを作成すると、
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
この部分だけが追加されるんですね。
どっちかにラベルの追加を記述すればいいのかなぁと思って試行錯誤していたのですが、セルを取得してもラベルがnilになったり、文字が重複して表示されたりしました。
下記の記事を参考にさせていただき無事解決しました。
【swift】UITableView,UITableViewCellを使って,カスタムセルのテーブルを作る.
UIPickerViewを追加しよう
PickerViewを設置します。
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource,UIPickerViewDelegate,UIPickerViewDataSource{
//ピッカービュー
private var pickerView:UIPickerView!
private let pickerViewHeight:CGFloat = 160
//pickerViewの上にのせるtoolbar
private var pickerToolbar:UIToolbar!
private let toolbarHeight:CGFloat = 40.0
//ピッカービューの選択肢
private let genderArray = ["男","女"]
private let animalArray = ["猫","犬","ライオン","カバ","キリン","ゾウ"]
private let sportsArray = ["野球","サッカー","バレーボール","テニス","水泳"]
//ピッカービューに渡すIndexPath
private var pickerIndexPath:IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let width = self.view.frame.width
let height = self.view.frame.height
//tableView
/* ~~ */
//pickerView
pickerView = UIPickerView(frame:CGRect(x:0,y:height + toolbarHeight,
width:width,height:pickerViewHeight))
pickerView.dataSource = self
pickerView.delegate = self
pickerView.backgroundColor = UIColor.gray
self.view.addSubview(pickerView)
//pickerToolbar
pickerToolbar = UIToolbar(frame:CGRect(x:0,y:height,width:width,height:toolbarHeight))
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneBtn = UIBarButtonItem(title: "完了", style: .plain, target: self, action: #selector(self.doneTapped))
pickerToolbar.items = [flexible,doneBtn]
self.view.addSubview(pickerToolbar)
}
PickerViewの上にツールバーを表示して、完了ボタンを設置しています。
キーボードの上にアクセサリーつけるみたいにできればいいのだけど上手くいかずとりあえずバラバラで追加してます…。
//列数
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if(pickerIndexPath != nil){
switch (pickerIndexPath.row){
case 0:
return genderArray.count
case 1:
return animalArray.count
case 2:
return sportsArray.count
default:
return 0
}
}else{
return 0
}
}
//labelのカスタマイズをする
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let label = UILabel()
label.textColor = UIColor.white
label.backgroundColor = UIColor.clear
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 18)
switch(pickerIndexPath.row){
case 0:
label.text = genderArray[row]
case 1:
label.text = animalArray[row]
case 2:
label.text = sportsArray[row]
default:
print("default")
}
return label
}
//列を選択した時
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let cell = tableView.cellForRow(at:pickerIndexPath) as! CustomTableViewCell
switch(pickerIndexPath.row){
case 0:
cell.rightLabel.text = genderArray[row]
case 1:
cell.rightLabel.text = animalArray[row]
case 2:
cell.rightLabel.text = sportsArray[row]
default:
cell.rightLabel.text = ""
}
}
//カラカラするところの列数
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
PickerViewは1つをすべてに使いまわしています。
それぞれで場合分けして列数、ラベルのテキストを返しています。
PickerViewのラベルをカスタマイズしない場合は、
//labelのカスタマイズをする
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
/* ~~ */
}
の部分を
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
// ここでラベル上のテキストだけを返す
switch (pickerIndexPath.row){
case 0:
return genderArray[row]
case 1:
return animalArray[row]
case 2:
return sportsArray[row]
default:
return 0
}
}
としてもokです。
TableViewとPickerViewをつなげよう
tableViewのセルを選択した際に、indexPathを変数に入れておいて、それをもとにpickerViewに表示するテキストを変更するようにします。
上記のpickerView部分は既に書いてありますが
switch (pickerIndexPath.row){
/* ~~ */
}
このpickerIndexPathで場合分けしています。
tableViewのカスタムセルの右側のラベルには初期値をいれておきます。
//現在の値
private var currentGender:String!
private var currentAnimal:String!
private var currentSports:String!
override func viewDidLoad() {
super.viewDidLoad()
//初期値
currentGender = genderArray[0]
currentAnimal = animalArray[0]
currentSports = sportsArray[0]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.textLabel?.text = textArray[indexPath.row]
print(indexPath.row)
//初期値
switch(indexPath.row){
case 0:
cell.rightLabel.text = currentGender
case 1:
cell.rightLabel.text = currentAnimal
case 2:
cell.rightLabel.text = currentSports
default:
cell.rightLabel.text = ""
}
return cell
}
PickerViewの選択後に現在の値を変更します。
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let cell = tableView.cellForRow(at:pickerIndexPath) as! CustomTableViewCell
switch(pickerIndexPath.row){
case 0:
cell.rightLabel.text = genderArray[row]
currentGender = genderArray[row]
case 1:
cell.rightLabel.text = animalArray[row]
currentAnimal = animalArray[row]
case 2:
cell.rightLabel.text = sportsArray[row]
currentSports = sportsArray[row]
default:
print("何もなし")
}
}
それぞれの現在のString(currentGender,currentAnimal,currentSportsのみ)に値を入れてtableViewをreloadしても良いかも。
tableViewのセルを選択した際の処理は
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
pickerIndexPath = indexPath
//ピッカービューをリロード
pickerView.reloadAllComponents()
//ピッカービューを表示
UIView.animate(withDuration: 0.2) {
self.pickerToolbar.frame = CGRect(x:0,y:self.view.frame.height - self.pickerViewHeight - self.toolbarHeight,
width:self.view.frame.width,height:self.toolbarHeight)
self.pickerView.frame = CGRect(x:0,y:self.view.frame.height - self.pickerViewHeight,
width:self.view.frame.width,height:self.pickerViewHeight)
}
}
pickerIndexPathに選択されたセルのindexPathをいれておき、pickerViewをリロードします。
そしてアニメーションで下からにゅっと表示しています。
閉じる時はツールバーに設置したdoneBtnで閉じます。
func doneTapped(){
UIView.animate(withDuration: 0.2){
self.pickerToolbar.frame = CGRect(x:0,y:self.view.frame.height,
width:self.view.frame.width,height:self.toolbarHeight)
self.pickerView.frame = CGRect(x:0,y:self.view.frame.height + self.toolbarHeight,
width:self.view.frame.width,height:self.pickerViewHeight)
}
//選択を解除する
self.tableView.deselectRow(at: pickerIndexPath, animated: true)
}
選択解除もしておきます。
これでとりあえずはokだと思うんですが、もうちょっと気になる点を改善します。
セルに表示されている文字からPickerViewをスタートさせる
例えば、セル上で「性別:男」となっている場合には、PickerViewを表示させた際に「男」が選択された状態であってほしいのです。
とりあえず便利なArrayから値が含まれているかどうか調べて、あればそのindexを返す関数をextensionで加えておきます。
extension Array {
func findIndex(includeElement: (Element) -> Bool) -> [Int] {
var indexArray:[Int] = []
for (index, element) in enumerated() {
if includeElement(element) {
indexArray.append(index)
}
}
return indexArray
}
}
pickerViewをリロードする直前に場合分けで列の選択をします。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
/* ~~ */
switch(indexPath.row){
case 0:
let index = genderArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:true)
}
case 1:
let index = animalArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:true)
}
case 2:
let index = sportsArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:true)
}
default:
pickerView.selectRow(0, inComponent: 0, animated: true)
}
//ピッカービューをリロード
pickerView.reloadAllComponents()
/* ~~ */
}
なんかもっと上手く書ける気がするんですがどうなんだろう…。
現在入っているセルのテキストが、それぞれの配列の何番めなのかを調べてその位置まで移動させています。
これでちゃんと動くと思うんですが、なんかときたま上手く挙動してくれない・・・うーむ。
なんか改善策あったらまた追記します(´・ω・`)
TableViewのセルとPickerViewが被らないようにスクロール
今回は被ってないんですが、セルがいっぱいになると画面下部のセルはPickerViewと被っちゃいます。
それをこんな感じでスクロールして回避します。
cellを選択した際に以下のように追加。
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//ピッカービューとセルがかぶる時はスクロール
let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
let cellLimit:CGFloat = cell.frame.origin.y + cell.frame.height
let pickerViewLimit:CGFloat = pickerView.frame.height + toolbarHeight
if(cellLimit >= pickerViewLimit){
UIView.animate(withDuration: 0.2) {
tableView.contentOffset.y = cellLimit - pickerViewLimit
}
}
/* ~~ */
}
そいで、完了ボタンを押した際に元の場所に戻るコードを追加。
func doneTapped(){
UIView.animate(withDuration: 0.2){
self.pickerToolbar.frame = CGRect(x:0,y:self.view.frame.height,
width:self.view.frame.width,height:self.toolbarHeight)
self.pickerView.frame = CGRect(x:0,y:self.view.frame.height + self.toolbarHeight,
width:self.view.frame.width,height:self.pickerViewHeight)
self.tableView.contentOffset.y = 0
}
self.tableView.deselectRow(at: pickerIndexPath, animated: true)
}
こんな感じで実装してみました。
PickerViewとTableViewの組み合わせってわりとありそうなのにイマイチ欲しい情報が見つからなくてちょっと苦戦しました。
書いている最中に、色々問題も見つけて書き加えたり訂正したりしているうちに随分読みにくくなってしまったかもしれないです。
最後に全体コードのっけて終わりにします。
全体コード
ViewController.swift
//
// ViewController.swift
// TablePicker
//
// Created by Reo on 2017/02/12.
// Copyright © 2017年 Reo. All rights reserved.
//
import UIKit
extension Array {
func findIndex(includeElement: (Element) -> Bool) -> [Int] {
var indexArray:[Int] = []
for (index, element) in enumerated() {
if includeElement(element) {
indexArray.append(index)
}
}
return indexArray
}
}
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource,UIPickerViewDelegate,UIPickerViewDataSource{
//テーブルビュー
private var tableView:UITableView!
//ピッカービュー
private var pickerView:UIPickerView!
private let pickerViewHeight:CGFloat = 160
//pickerViewの上にのせるtoolbar
private var pickerToolbar:UIToolbar!
private let toolbarHeight:CGFloat = 40.0
//テーブルビューのテキストラベル
private let textArray = ["性別","好きな動物","好きなスポーツ"]
//ピッカービューの選択肢
private let genderArray = ["男","女"]
private let animalArray = ["猫","犬","ライオン","カバ","キリン","ゾウ"]
private let sportsArray = ["野球","サッカー","バレーボール","テニス","水泳"]
//ピッカービューに渡すIndexPath
private var pickerIndexPath:IndexPath!
//現在の値
private var currentGender:String!
private var currentAnimal:String!
private var currentSports:String!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let width = self.view.frame.width
let height = self.view.frame.height
//初期値
currentGender = genderArray[0]
currentAnimal = animalArray[0]
currentSports = sportsArray[0]
//tableView
tableView = UITableView(frame: CGRect(x:0,y:50,width:width,height:height - 50))
tableView.delegate = self
tableView.dataSource = self
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(tableView)
//pickerView
pickerView = UIPickerView(frame:CGRect(x:0,y:height + toolbarHeight,
width:width,height:pickerViewHeight))
pickerView.dataSource = self
pickerView.delegate = self
pickerView.backgroundColor = UIColor.gray
self.view.addSubview(pickerView)
//pickerToolbar
pickerToolbar = UIToolbar(frame:CGRect(x:0,y:height,width:width,height:toolbarHeight))
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let doneBtn = UIBarButtonItem(title: "完了", style: .plain, target: self, action: #selector(self.doneTapped))
pickerToolbar.items = [flexible,doneBtn]
self.view.addSubview(pickerToolbar)
}
func doneTapped(){
UIView.animate(withDuration: 0.2){
self.pickerToolbar.frame = CGRect(x:0,y:self.view.frame.height,
width:self.view.frame.width,height:self.toolbarHeight)
self.pickerView.frame = CGRect(x:0,y:self.view.frame.height + self.toolbarHeight,
width:self.view.frame.width,height:self.pickerViewHeight)
self.tableView.contentOffset.y = 0
}
self.tableView.deselectRow(at: pickerIndexPath, animated: true)
}
/********************** TableView **********************/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return textArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.textLabel?.text = textArray[indexPath.row]
print(indexPath.row)
//初期値
switch(indexPath.row){
case 0:
cell.rightLabel.text = currentGender
case 1:
cell.rightLabel.text = currentAnimal
case 2:
cell.rightLabel.text = currentSports
default:
cell.rightLabel.text = ""
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//ピッカービューとセルがかぶる時はスクロール
let cell = tableView.cellForRow(at: indexPath) as! CustomTableViewCell
let cellLimit:CGFloat = cell.frame.origin.y + cell.frame.height
let pickerViewLimit:CGFloat = pickerView.frame.height + toolbarHeight
if(cellLimit >= pickerViewLimit){
print("位置変えたい")
UIView.animate(withDuration: 0.2) {
tableView.contentOffset.y = cellLimit - pickerViewLimit
}
}
switch(indexPath.row){
case 0:
let index = genderArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:false)
}
case 1:
let index = animalArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:false)
}
case 2:
let index = sportsArray.findIndex{$0 == cell.rightLabel.text}
if(index.count != 0){
pickerView.selectRow(index[0],inComponent:0,animated:false)
}
default:
pickerView.selectRow(0, inComponent: 0, animated: false)
}
pickerIndexPath = indexPath
//ピッカービューをリロード
pickerView.reloadAllComponents()
//ピッカービューを表示
UIView.animate(withDuration: 0.2) {
self.pickerToolbar.frame = CGRect(x:0,y:self.view.frame.height - self.pickerViewHeight - self.toolbarHeight,
width:self.view.frame.width,height:self.toolbarHeight)
self.pickerView.frame = CGRect(x:0,y:self.view.frame.height - self.pickerViewHeight,
width:self.view.frame.width,height:self.pickerViewHeight)
}
}
/********************* PickerView ***********************/
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if(pickerIndexPath != nil){
switch (pickerIndexPath.row){
case 0:
return genderArray.count
case 1:
return animalArray.count
case 2:
return sportsArray.count
default:
return 0
}
}else{
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let label = UILabel()
label.textColor = UIColor.white
label.backgroundColor = UIColor.clear
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 18)
switch(pickerIndexPath.row){
case 0:
label.text = genderArray[row]
case 1:
label.text = animalArray[row]
case 2:
label.text = sportsArray[row]
default:
print("なし")
}
return label
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let cell = tableView.cellForRow(at:pickerIndexPath) as! CustomTableViewCell
switch(pickerIndexPath.row){
case 0:
cell.rightLabel.text = genderArray[row]
currentGender = genderArray[row]
case 1:
cell.rightLabel.text = animalArray[row]
currentAnimal = animalArray[row]
case 2:
cell.rightLabel.text = sportsArray[row]
currentSports = sportsArray[row]
default:
print("何もなし")
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
CustomTableViewCell.swift
//
// CustomTableViewCell.swift
// TablePicker
//
// Created by Reo on 2017/02/12.
// Copyright © 2017年 Reo. All rights reserved.
//
import UIKit
class CustomTableViewCell: UITableViewCell {
public var rightLabel:UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
rightLabel = UILabel()
rightLabel.frame = CGRect(x:self.frame.width / 2,y:0,width:self.frame.width / 2,height:self.frame.height)
rightLabel.textAlignment = .center
self.accessoryView = rightLabel
}
}
参考リンク
Additional Notes追記
Gistにあげました。
一応少しだけ修正してSwift4に対応したものをGistにあげておきました。
いやーーーー。 ここだけの話、今ある全てのSwift記事をSwift4に対応させる作業中なんですけど、この記事を一番最後まで残してあったんですよね。重くて。
もっとキレイに書きたくて後回しにする判断をしたんですけど、ダメでした。力尽きました。 とりあえず最低限extensionに移すぐらいしかできませんでした。
一応動作はちゃんとします。 もうちょっとキレイに書きたかった。でももう頑張れない。
また気が向いた時に修正するかもしれません。今はこれで失礼します。
コメントはありません。
現在コメントフォームは工事中です。