接著前天的,我們已經把頁面上需要的元件關聯都設定好了,所以剩下的部分都可以單純的在AddEventViewController中完成,這邊先提一下,因為在開發中的過程,不斷有做不同的調整,所以偶爾會看到畫面或程式碼怎麼跟前幾天的不一樣了?!這是難免的啦XD,但我有盡量在有異動後去調整先前的文章,若有遺漏的,請大家多包涵~
今天這篇會講幾個東西
在我們的Add New Event畫面上會用到的日期元件有三個:Start Date、End Date、Time,因此我們會需要建立以下的變數
let startDatePicker = UIDatePicker() // Start Date的DatePicker
let endDatePicker = UIDatePicker() // End Date的DatePicker
let timePicker = UIDatePicker() // Time的DatePicker
let showDateFormatter = DateFormatter() // 要顯示出來看的日期格式
let showTimeFormatter = DateFormatter() // 要顯示出來看的時間格式
let dateFormatter = DateFormatter() // 欲儲存進資料庫的日期格式
let timeFormatter = DateFormatter() // 欲儲存進資料庫的時間格式
接著,在viewDidLoad()的時候加入各自的設定
// 設置時間顯示的格式
showDateFormatter.dateFormat = "yyyy-MM-dd"
showTimeFormatter.dateFormat = "HH:mi"
dateFormatter.dateFormat = "yyyyMMdd"
timeFormatter.dateFormat = "HHmi"
// 設置 UIDatePicker 格式,StartDate為日期類型
startDatePicker.datePickerMode = .date
// 設置顯示的語言環境,台灣
startDatePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
startDatePicker.addTarget(self, action: #selector(changeStartDate), for: .valueChanged)
// 將DatePicker綁定到TextField上
startDate.inputView = startDatePicker
// 因為起始日為必填值,因此給他初始化今天日期
changeStartDate()
// 設置 UIDatePicker 格式,StartDate為日期類型
endDatePicker.datePickerMode = .date
// 設置顯示的語言環境,台灣
endDatePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
endDatePicker.addTarget(self, action: #selector(changeEndDate), for: .valueChanged)
// 將DatePicker綁定到TextField上
endDate.inputView = endDatePicker
// 設置 UIDatePicker 格式,StartDate為時間類型
timePicker.datePickerMode = .time
// 設置顯示的語言環境,台灣
timePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
timePicker.addTarget(self, action: #selector(changeExecTime), for: .valueChanged)
// 將DatePicker綁定到TextField上
execTime.inputView = timePicker
然後,我們在每個DatePicker上都加入了當值改變(.valueChanged)就要呼叫各自的方法(#selector(xxxxx)),方法內容其實也只是要把DatePicker的內容顯示在TextField上,不然選完就看不到選了什麼
@objc func changeStartDate() {
startDate.text = showDateFormatter.string(from: startDatePicker.date)
}
@objc func changeEndDate() {
endDate.text = showDateFormatter.string(from: endDatePicker.date)
}
@objc func changeExecTime() {
execTime.text = showTimeFormatter.string(from: timePicker.date)
}
這樣....就完成了喔!!!有沒有很快!
結果的畫面如下
這個元件,我們用在Category上,且當初我設計的時候有故意保留未來可以給使用這自己設定的空間,因此我會搭配著Property List一起使用,先來看Picker吧
一樣先命名變數
// 顯示選單的Picker
let pickerView = UIPickerView()
// Picker內的參數,從plist中取得
var pickerData: [(code: Int, name: String)]! = []
// 被選取的選項,對應要寫入資料庫的值
var categoryCode: Int = 0
接著於viewDidLoad()中加入設定
// 設置顯示選取的資料,例如:選取了Job,再次開啟Picker時,就會顯示在Job上
pickerView.showsSelectionIndicator = true
pickerView.delegate = self
pickerView.dataSource = self
// 將PickerView加入對應的TextField中
categoryPicker.inputView = pickerView
這邊要注意一下,因為delegate和dataSource都指向self,也就是viewController本身,因此要記得加入協定
class AddEventViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
然後設定PickerView的相關功能以及協定需寫入的funciton
// Sets number of columns in picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
// Sets the number of rows in the picker view
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return pickerData.count
}
// This function sets the text of the picker view to the content of the "salutations" array
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return pickerData[row].name
}
// When user selects an option, this function will set the text of the text field to reflect the selected option.
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// 將被選中的資料name顯示至畫面、code存於變數,之後寫入資料庫要使用
categoryPicker.text = pickerData[row].name
categoryCode = pickerData[row].code
}
那基本上,PickerView的部份這樣就完成了喔!只是因為現在沒有資料在pickerData裡面,所以都會是空的
先新建一個Property List 檔案,命名為Category.plist
接著左側選取該檔案,後於右側可圖像編輯加入資料,這裡使用Array包Dictionary的方式,然後Dictionary裡面分別有code與name兩個key-value
建立完成後,回到AddEventViewController.viewDidLoad()裡面,要把資料撈出來
// 撈取設定檔的pickerData
if let path = Bundle.main.path(forResource: "Category", ofType: "plist"),
let array = NSArray(contentsOfFile: path) {
// Use your myDict here
for case let category as NSDictionary in array {
pickerData.append((code:category.object(forKey: "code") as! Int, name: category.object(forKey: "name") as! String))
}
}
終於到新增資料進資料庫了!當點選右上角的「Save」按鈕,就會儲存資料,程式碼寫在前天建立出來的function save()裡,在寫入DB之前有幾項資料檢核需要先做
if nameText.text! == "" {
confirmAlert("Name is required!")
} else if startDate.text == "" {
confirmAlert("Start Date is required!")
} else if repeatButton.selectedSegmentIndex != 0 && endDate.text == "" {
confirmAlert("If event is routine, end date is required.")
} else if endDate.text != "" && endDatePicker.date <= startDatePicker.date {
confirmAlert("End date cannot same with or earlier than start date.")
} else {
// valid
}
如果資料都正確,就可以準備寫入,在AddEventViewController要使用SQLiteManager,是可以用Create一個新的方式,但每次建立一個新的,就會執行一次init(),建立一次連線,這樣多次下來,難保不會有狀況,因此我把它置於APP一起動時就建立(AppDelegate.swift),讓不同的ViewController也都可以調用同一個
再回到AddEventViewController中加入,就可以使用了喔
let sqlManager = (UIApplication.shared.delegate as! AppDelegate).sqlManager
接著,因為我們有四種repeat方式,我們就刻意來使用昨天有提到的Enum類型吧
enum RepeatType: Int {
case Once = 0
case Daily = 1
case Weekly = 2
case Monthly = 3
}
這不同的Repeat方式需要計算日期,因此使用Calendar來協助日期與月份的加減,就可以避開幾月大幾月小和可怕的閏年非閏年的問題,很方便,然後搭配do-while迴圈寫入,這個邏輯不難,身為工程師的大家應該可以輕鬆理解
let calendar = Calendar.current
var execDate = startDatePicker.date
// Repeat
repeat {
// insert table
switch repeatButton.selectedSegmentIndex {
case RepeatType.Once.rawValue:
// Repeat == Once
break
case RepeatType.Daily.rawValue:
// Repeat == Daily
execDate = calendar.date(byAdding: .day, value: 1, to: execDate)!
case RepeatType.Weekly.rawValue:
// Repeat == Weekly
execDate = calendar.date(byAdding: .day, value: 7, to: execDate)!
case RepeatType.Monthly.rawValue:
// Repeat == Monthly
execDate = calendar.date(byAdding: .month, value: 1, to: execDate)!
default:
print("error repeat type")
}
} while endDate.text != "" && execDate <= endDatePicker.date
然後,寫入資料庫就呼叫SQLiteManager裡面的方法吧
sqlManager.insertEvent(day: dateFormatter.string(from: execDate), name: nameText.text!, time: execTimeText, category: categoryCode, reminder: reminderSwitch.isOn, place: placeText.text, lat: lat, lng: lng, note: noteText.text)
最後,新增完成要關掉Add New Event的View
dismiss(animated: true, completion: nil)
如此就完成了喔!
附上完整的save()程式碼:
@IBAction func save(_ sender: UIBarButtonItem) {
if nameText.text! == "" {
confirmAlert("Name is required!")
} else if startDate.text == "" {
confirmAlert("Start Date is required!")
} else if repeatButton.selectedSegmentIndex != 0 && endDate.text == "" {
confirmAlert("If event is routine, end date is required.")
} else if endDate.text != "" && endDatePicker.date <= startDatePicker.date {
confirmAlert("End date cannot same with or earlier than start date.")
} else {
// 若有設定時間,才寫入HHMi格式,否則為nil
var execTimeText: String? = nil
if execTime.text != "" {
execTimeText = timeFormatter.string(from: timePicker.date)
}
let calendar = Calendar.current
var execDate = startDatePicker.date
// Repeat
repeat {
sqlManager.insertEvent(day: dateFormatter.string(from: execDate), name: nameText.text!, time: execTimeText, category: categoryCode, reminder: reminderSwitch.isOn, place: placeText.text, lat: lat, lng: lng, note: noteText.text)
switch repeatButton.selectedSegmentIndex {
case RepeatType.Once.rawValue:
// Repeat == Once
break
case RepeatType.Daily.rawValue:
// Repeat == Daily
execDate = calendar.date(byAdding: .day, value: 1, to: execDate)!
case RepeatType.Weekly.rawValue:
// Repeat == Weekly
execDate = calendar.date(byAdding: .day, value: 7, to: execDate)!
case RepeatType.Monthly.rawValue:
// Repeat == Monthly
execDate = calendar.date(byAdding: .month, value: 1, to: execDate)!
default:
print("error repeat type")
}
} while endDate.text != "" && execDate <= endDatePicker.date
dismiss(animated: true, completion: nil)
}
}