#How to use UIPickerView in Swift
目的: 學習使用 UIPickerView
第一步 在 storyboard 添加 Picker View
第二步 從 Picker View 拉線到 View Controller
方法 1. 直接拉線
dataSource ✅
delegate ✅
⭐ 方法 2. 透過程式
先拉 @IBOutlet weak var PickerView: UIPickerView!
接下來在 override func viewDidLoad() 輸入以下程式
override func viewDidLoad() {
super.viewDidLoad()
PickerView.delegate = self
PickerView.dataSource = self
第三步 遵從 protocol ,並宣告 protocol 的 func()
在 ViewController 新增 UIPickerViewDelegate 、UIPickerViewDataSource
class ViewController: UIViewController , UIPickerViewDelegate, UIPickerViewDataSource {
點選 Fix,讓 Xcode 幫你寫出 func
第四步 開始測試
在 第一個 func numberOf…() 回傳 1,第二個 func pickerView(…)回傳 5
//決定 pickerview 區域數量
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
//決定 pickerview 區域行數
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 5
}
執行後我們會看到有 1 個區域,且有 5 個問號
新增以下 func 傳入資料 “test”
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
"test"
}
新增自訂資料 ,並且更改回傳程式
let cityArray = ["", "台北市", "新北市", "高雄市"]
let TaipeiDistArray = ["松山區", "大安區", "士林區", "信義區"]//決定pickerview 區域行數
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return cityArray.count
}
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return cityArray[row]
}
新增第二個區域
關鍵參數 component 組件編號,component 是由 0 開始 由左至右編號
//決定pickerview 區域數量
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
//決定pickerview 區域行數
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0{
return cityArray.count
}else {
return TaipeiDistArray.count
}
}
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0{
return cityArray[row]
}else {
return TaipeiDistArray[row]
}
}
如果不熟悉的話,可以透過以下程式來加深印象
//決定pickerview 區域數量
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
//決定pickerview 區域行數
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 4
}
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return "component \(component) row \(row)"
}
讓每個縣市與區域同步
將資料改為自訂義 class ,新增 Swift File CityandDist
class CityandDist {
var city: String
var dist: [String]
init(city: String, dist: [String]) {
self.city = city
self.dist = dist
}
}
在 ViewController 新增 Array
let cityanddistarray = [
CityandDist(city: "台北市", dist: ["松山區", "大安區", "士林區", "信義區"]),
CityandDist(city: "新北市", dist: ["林口區", "新莊區", "三重區", "中和區", "永和區"]),
CityandDist(city: "高雄市", dist: ["楠梓區", "左營區", "鼓山區", "三民區", "鹽埕區", "前金區", "新興區", "苓雅區", "前鎮區", "小港區", "旗津區", "鳳山區", "大寮區", "鳥松區", "林園區", "仁武區", "大樹區", "大社區", "岡山區", "路竹區", "橋頭區", "梓官區", "彌陀區", "永安區"])
]]
右邊的 row 的數量及資料,會由左邊我們所選到的城市來決定,所以 selectedRow(inComponent: 0) 參數會填入 0
//決定pickerview 區域行數
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0{
return cityanddistarray.count
}else {
let selectcity = pickerView.selectedRow(inComponent: 0)
return cityanddistarray[selectcity].dist.count
}
}
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0{
return cityanddistarray[row].city
}else {
let selectcity = pickerView.selectedRow(inComponent: 0)
return cityanddistarray[selectcity].dist[row]
}
}
執行後會發現程式沒有達到我們預期希望的執行結果
這時候就需要以下程式碼來幫助我們
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadComponent(1)
}
成功了!
來試試看取所選資料與 Label 做連結
@IBOutlet weak var cityLabel: UILabel!
@IBOutlet weak var distLabel: UILabel!
改寫以下 func()
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadComponent(1)
let selectcity = pickerView.selectedRow(inComponent: 0)
let selectdist = pickerView.selectedRow(inComponent: 1)
let city = cityanddistarray[selectcity].city
let dist = cityanddistarray[selectcity].dist[selectdist]
print("city: \(city)")
print("dist: \(dist)")
cityLabel.text = city
distLabel.text = dist
}
看起來似乎沒有問題了對吧?我們再多試幾次看看
是什麼造成閃退的呢?
用左邊的圖中操作來看看出現的錯誤
出現了 index out of range ,原來是我們 選擇新北市的地區時,新北市的資料比台北市多了一筆,當我們新北市選擇最後一個地區,再選回台北市時,台北市的地區 dist[selectdist] 沒有第五筆的資料會造成閃退
解決方法
讓我們思考一下,是什麼造成閃退的?由於每個縣市的地區數量不一定匹配所造成的閃退,那每次更新城市都讓地區跳回第一個 row,不就不會閃退了對吧?讓我們來改寫一下程式
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
//讓每個地區回到第一列
pickerView.selectRow(0, inComponent: 1, animated: true)
}
let selectcity = pickerView.selectedRow(inComponent: 0)
let selectdist = pickerView.selectedRow(inComponent: 1)
let city = cityanddistarray[selectcity].city
let dist = cityanddistarray[selectcity].dist[selectdist]
print("city: \(city)")
print("dist: \(dist)")
cityLabel.text = city
distLabel.text = dist
}
終於成功了!
但真的都沒有問題了嗎?我們再多測試看看
怎麼又閃退了?原來是我們如果沒有等地區停止就快速切換城市的話,也會造成閃退 Index out of range
那如果我們能夠在滑動地區時,限制住畫面中城市,不讓它任意滑動的話,是不是就能解決了?
先將以下程式新增在 ViewController ,func isScrolling () 能幫助我們判斷 PickerView 是否在捲動
//判斷手反在捲動
extension UIView {
func isScrolling () -> Bool {if let scrollView = self as? UIScrollView {
if (scrollView.isDragging || scrollView.isDecelerating) {
return true
}
}
for subview in self.subviews {
if subview.isScrolling() {
return true
}
}
return false
}
}
接下來在以下程式加入程式碼,關鍵程式 isUserInteractionEnabled 這能夠讓 PickerView 禁止滑動
//決定pickerview 區域的每筆資料
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if pickerView.isScrolling() {
pickerView.isUserInteractionEnabled = false
} else {
pickerView.isUserInteractionEnabled = true
}
if component == 0 {
return cityanddistarray[row].city
}else {
let selectcity = pickerView.selectedRow(inComponent: 0)
return cityanddistarray[selectcity].dist[row]
}
}
當我們滑動時,限制我們不能任意再次滑動畫面,當滾輪停止時,我們就能再次滑動
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.isUserInteractionEnabled = true
if component == 0 {
pickerView.reloadComponent(1)
//讓每個地區回到第一列
pickerView.selectRow(0, inComponent: 1, animated: true)
}
let selectcity = pickerView.selectedRow(inComponent: 0)
let selectdist = pickerView.selectedRow(inComponent: 1)
let city = cityanddistarray[selectcity].city
let dist = cityanddistarray[selectcity].dist[selectdist] print("city: \(city)")
print("dist: \(dist)")
cityLabel.text = city
distLabel.text = dist
}
太棒了!終於成功了!當然使用 UIPickerView 的方法有很多種,為了弄清楚使用這個元件花了不少的時間,比較不熟悉的是資料的部分,我有看到有人用特別的資料寫法,在快速移動地區再切換城市時,不會造成閃退,當然每個人都有喜好,每個人的程式都不同才有趣,之後會繼續深入配合其他元件做出變化,如果有人想要交流,歡迎留言謝謝。