開發多個 column 的 UIPickerView — 以選擇台灣的縣市行政區為例
在 iOS App 輸入資料時,我們時常會看到長得像滾輪的 UIPickerView。UIPickerView 可以只有一排,但也可以多排,比方下圖台鐵 App 的 picker view 從第一排選擇地區,第二排選擇火車的站名。
製作多個 column 的 UIPickerView 會比單排複雜許多。接下來彼得潘將以搬家為例,說明如何利用兩個 column 的 picker view 選擇台灣的縣市 & 行政區。
我們想要製做的 App 畫面如下,我們的目標是搬到台北市大安區的豪宅,先假設我們即將中樂透,不用擔心錢的問題。(不過畫面的背景似乎有點像鬼屋)
加入台灣行政區的 JSON 檔,定義 JSON 對應的資料型別 & array
參考以下連結的說明操作。
asset 裡的 taiwanDistricts 儲存台灣行政區的 JSON 資料。
下圖顯示 JSON 的部分內容。
定義 JSON 對應的資料型別 & array data。
import UIKit
struct City: Decodable {
let name: String
let districts: [District]
}
struct District: Decodable {
let zip: String
let name: String
}
extension City {
static var data: [Self] {
guard let data = NSDataAsset(name: "taiwanDistricts")?.data else {
return []
}
return (try? JSONDecoder().decode([Self].self, from: data)) ?? []
}
}
在畫面上加入 picker view,label,image view & button
我們將在兩個地方顯示從 picker 選擇的行政區,在 label & 點選 button 顯示的 alert 上。
將 view controller 設為 picker view 的 dataSource & delegate
連結 outlet,action & 宣告 property cities 儲存行政區的資料
class ViewController: UIViewController {
@IBOutlet weak var districtPickerView: UIPickerView!
@IBOutlet weak var districtLabel: UILabel!
let cities = City.data
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func showAlert(_ sender: Any) {
}
}
定義 protocol UIPickerViewDataSource & UIPickerViewDelegate 的相關 function
extension ViewController: UIPickerViewDataSource, UIPickerViewDelegate {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return cities.count
} else {
let cityRow = pickerView.selectedRow(inComponent: 0)
return cities[cityRow].districts.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return cities[row].name
} else {
let cityRow = pickerView.selectedRow(inComponent: 0)
return cities[cityRow].districts[row].name
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
pickerView.selectRow(0, inComponent: 1, animated: true)
}
}
}
說明
func numberOfComponents(in pickerView: UIPickerView) -> Int {
2
}
設定 picker view 有兩排,第一排顯示縣市清單,第二排顯示縣市對應的行政區清單。
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return cities.count
} else {
let cityRow = pickerView.selectedRow(inComponent: 0)
return cities[cityRow].districts.count
}
}
設定 picker view 每排顯示的資料數量。第一排的數量是縣市的數量,第二排的數量是選取的縣市對應的行政區數量。我們必須先取得第一排選取的縣市,才能設定第二排顯示的行政區數量,比方台北市有 12 個行政區,高雄市有 40 個行政區。
我們利用 pickerView.selectedRow(inComponent: 0)
取得第一排選取的 row number,比方當 row number 為 0 時,表示選到台北市,而台北市有 12 個行政區,因此 cities[cityRow].districts.count
會回傳 12。
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if component == 0 {
return cities[row].name
} else {
let cityRow = pickerView.selectedRow(inComponent: 0)
return cities[cityRow].districts[row].name
}
}
設定 picker view 顯示的字串,第一排顯示縣市清單,第二排顯示縣市對應的行政區清單。
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
pickerView.selectRow(0, inComponent: 1, animated: true)
}
}
當使用者滑動 picker,picker 停止滑動時會呼叫 pickerView(_:didSelectRow:inComponent:)
。當第一排選取的縣市改變時,我們必須更新第二排的行政區。比方三民區在高雄市,若是沒有更新,讓三民區出現在台北市,那可就要天下大亂了。
當 component 為 0 時,表示選取的縣市改變,因此我們從 pickerView 呼叫 reloadComponent(1)
更新第二排行政區的內容,然後呼叫 selectRow(0, inComponent: 1, animated: true)
讓第二排滑到第一筆,確保每次縣市改變時,都會顯示縣市的第一個行政區。
結果
Cool,現在我們的 picker view 已經可以正確顯示縣市跟行政區,而且選取的縣市改變時,行政區也會立即更新。
滑動 picker,停止滑動時在 label 顯示選取的內容
從 pickerView 呼叫 selectedRow(inComponent: 0)
& selectedRow(inComponent: 1)
,取得第一排跟第二排選取的 row number,然後再從 cities 取得對應的縣市跟行政區。
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if component == 0 {
pickerView.reloadComponent(1)
pickerView.selectRow(0, inComponent: 1, animated: true)
}
let cityRow = pickerView.selectedRow(inComponent: 0)
let districtRow = pickerView.selectedRow(inComponent: 1)
districtLabel.text = "\(cities[cityRow].name)\(cities[cityRow].districts[districtRow].name)"
}
點擊 button 時顯示 alert
從 districtPickerView 呼叫 selectedRow(inComponent: 0)
& selectedRow(inComponent: 1)
,取得第一排跟第二排選取的 row number,然後再從 cities 取得對應的縣市跟行政區。
@IBAction func showAlert(_ sender: Any) {
let cityRow = districtPickerView.selectedRow(inComponent: 0)
let districtRow = districtPickerView.selectedRow(inComponent: 1)
let message = "\(cities[cityRow].name)\(cities[cityRow].districts[districtRow].name)"
let alertController = UIAlertController(title: "我想搬到", message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default)
alertController.addAction(okAction)
present(alertController, animated: true)
}