Một số kỹ thuật về Event Kit trong lập trình iOS
Request Permission
EvenKit là 1 framework giúp chúng ta truy cập và sử dụng Calendar và Reminder của người dùng. Kỹ thuật đầu tiên là cách xin quyền user truy cập vào Calendar và Reminder (Request Permission) cũng như là cách kiểm tra người dùng có cho phép chúng ta truy cập vào chúng hay không.
Đầu tiên, chúng ta sẽ khởi tạo biến eventStore có kiểu EKEventStore. EKEventStore là trung tâm trong EventKit, cho phép chúng ta đọc hoặc ghi vào Calendar và Reminder của người dùng.
1 | let eventStore = EKEventStore() |
Tiếp theo, chúng ta sẽ kiểm tra quyền hạn App có được phép truy cập vào Calendar hay không. Điều này cực kỳ quan trọng, ảnh hưởng trực tiếp đến sự “sống còn” của App, cần thực hiện mỗi khi màn hình hiện lên (viewDidAppear/ viewWillAppear).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) checkCalendarAuthorizationStatus() } func checkCalendarAuthorizationStatus() { let status = EKEventStore.authorizationStatus(for: .event) switch (status) { case .notDetermined: break // Lần đầu vào app case .authorized: break // User đã cho phép sử dụng case .restricted, .denied: break // User chưa cho phép/ bỏ qua } } |
Công việc tiếp theo là tạo ra 1 func requestAccessToCalendar và sau đó đưa vào case .notDetermined
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | func requestAccessToCalendar() { eventStore.requestAccess(to: .event) { (isAllowed, error) in if error != nil { print(error?.localizedDescription) } else { if isAllowed { print("User Allowed") } else { print("User Denied") } } } } |
Ở bước tiếp theo, chúng ta cần 1 mảng chứa tất các các event của User
1 | var calendars: [EKCalendar] = [EKCalendar]() |
Và sau đó, tạo func để load tất cả dữ liệu của User vào mảng
1 2 3 4 | func loadCalendars() { self.calendars = eventStore.calendars(for: .event) print(calendars) } |
Tiếp theo, chúng ta sẽ bỏ func trên vào case authorized. Sau khi thiếp lập tất cả các dòng lệnh trên, chúng ta sẽ run App và chờ xem điều gì sẽ xảy ra.
Chúng ta sẽ quay lại file info.plist và xét quyền vào cho app của chúng ta là xong.
Lấy danh sách các Event
Chúng ta sẽ tiếp tục lấy các event từ các danh muc lấy được và đổ chúng vào UITableView.
Đầu tiên, chúng ta tạo UITableView và đổ các danh mục vào trước.
1 2 3 4 5 6 7 8 9 10 11 12 | extension ViewController : UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return calendars.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) let event = calendars[indexPath.row] cell.textLabel?.text = event.title return cell } } |
Chúng ta cần tạo thêm 1 ViewController nữa, và đặt Storyboard ID cho nó là EventVC.
Bước tiếp theo, chúng ta sẽ bắt sự kiện User chọn vào 1 cell nào đó, và chuyển màn hình và hiển thị các event thuộc calendar đó (Calendar, US Holidays, Birthdays).
1 2 3 4 5 6 7 8 9 | extension ViewController : UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: false) guard let vc = self.storyboard?.instantiateViewController(withIdentifier: "EventVC") as? EventViewController else {return} vc.calendar = calendars[indexPath.row] self.navigationController?.pushViewController(vc, animated: true) } } |
Để có thể gọi được vc.calendar, cần khởi tạo nó ở EventViewController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | var calendar : EKCalendar? func loadEvents(calendar: EKCalendar) { // DateFormatter: dùng để chuyển kiểu String sang kiểu Date và ngược lại let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" // Chúng ta chỉ muốn lấy các event nằm trong khoảng thời gian nào đó, chúng không phải load lên hết let startDate = dateFormatter.date(from: "2016-01-01") let endDate = dateFormatter.date(from: "2016-12-31") if let startDate = startDate, let endDate = endDate { let eventStore = EKEventStore() // Dùng eventStore để tạo và thiết lập thuộc tính cho NSPredicate let eventsPredicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: [calendar]) // Dùng NSPredicate vừa thiết lập để tìm ra những event phù hợp với điều kiện đưa ra self.events = eventStore.events(matching: eventsPredicate).sorted(){ (e1: EKEvent, e2: EKEvent) -> Bool in return e1.startDate.compare(e2.startDate) == ComparisonResult.orderedAscending } print(events) self.tableView.reloadData() } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) loadEvents(calendar: calendar!) } |
Tới bước này, chúng ta đã lấy được tất cả sự kiện từ đầu năm 2016 đến cuối năm 2016. Và công việc còn lại của các bạn chỉ là từ dữ liệu nhận được mà đổ chúng vào tableview là xong.
Chúc các bạn thành công!!!
Tạo Calendar
Đầu tiên, chúng ta cần tạo thêm 1 màn hình và 1 file ViewController nữa, để đơn giản chúng ta chỉ cần 1 textfield và 1 button là xong. Và công việc lại lẩn quẩn là ánh xạ, đặt tên. Để quy chuẩn lại cách đặt tên của các bạn trong bài viết này, mình sẽ đặt tên như sau:
- NewCalendarViewController
- calendarTitleTextField
addNewCalendarActionButton
Ở addNewCalendarActionButton, chúng ta sẽ:
- Khởi tạo biến eventStore có kiểu dữ liệu là EKEventStore
- Khởi tạo biến newCalendar có kiểu dữ liệu là EKCalendar
- Xét title cho biến newCalendar bằng với nội dung của user nhập vào
- Xét source cho newCalendar
- Save Calendar
- Xử lý nếu có lỗi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | @IBAction func addNewCalendarActionButton(_ sender: AnyObject) { guard let calendarTitle:String = calendarTitleTextField.text else {return} if calendarTitle == "" { return } let eventStore = EKEventStore() let newCalendar = EKCalendar(for: .event, eventStore: eventStore) newCalendar.title = calendarTitle // Lấy các source từ biến eventStore let sourcesInEventStore = eventStore.sources // Lọc ra những source có type là local, và chỉ lấy phần tử đầu tiên và add vào source của newCalendar newCalendar.source = sourcesInEventStore.filter{ (source: EKSource) -> Bool in source.sourceType.rawValue == EKSourceType.local.rawValue }.first! // Save Calendar vừa tạo do { try eventStore.saveCalendar(newCalendar, commit: true) let _ = self.navigationController?.popViewController(animated: true) } catch { // Show bảng thông báo khi save calendar mới thất bại let alert = UIAlertController(title: "Calendar could not save", message: (error as NSError).localizedDescription, preferredStyle: .alert) let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil) alert.addAction(OKAction) self.present(alert, animated: true, completion: nil) } } |
Ở đoạn code
1 2 3 4 5 6 7 8 | // Lấy các source từ biến eventStore let sourcesInEventStore = eventStore.sources // Lọc ra những source có type là local, và chỉ lấy phần tử đầu tiên và add vào source của newCalendar newCalendar.source = sourcesInEventStore.filter{ (source: EKSource) -> Bool in source.sourceType.rawValue == EKSourceType.local.rawValue }.first! |
húng ta không tạo 1 biến EKSource, mà sẽ lấy chúng ra từ biến eventStore để có thể sử dụng được các phương thức có sẵn của đối tượng nguồn
Sau khi nhập tất cả dòng code trên, run App và sẽ thấy ngay kết quả:
Vậy là chúng ta đã có thể tạo thêm 1 Calendar mới cho điện thoại cũng như App của chúng ta.
Tạo Event vào Calendar
Chúng ta sẽ tạo thêm 1 màn hình có tên là NewEventViewController.swift và kéo vào thêm 1 UIViewController trong Main.storyboardstoryboard, sẵn tiện chúng ta sẽ đặt cho nó StoryBoard ID là newEventVC
Sau đó ở EventViewController.swift, chúng ta thêm đoạn code như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self // Do any additional setup after loading the view. // 1. Tạo button "+" vào màn hình let addEventButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewEvent)) self.navigationItem.rightBarButtonItem = addEventButton } //2. Chuyển sang màn hình addNewEvent func addNewEvent() { guard let newEventVC = self.storyboard?.instantiateViewController(withIdentifier: "newEventVC") as? NewEventViewController else {return} self.navigationController?.pushViewController(newEventVC, animated: true) } |
Như đã chú thích ở trên, chúng ta sẽ tạo thêm button “+” vào góc trên bên phải màn hình và khi nhấn vào nó chúng ta sẽ chuyển sang màn hình NewEvent. Chúng ta sẽ kéo giao diện vào cho màn hình New Event.
Đây là những gì bạn cần:
1 2 3 4 5 6 7 8 9 10 11 12 | @IBOutlet weak var eventNameTextField: UITextField! @IBOutlet weak var startDatePicker: UIDatePicker! @IBOutlet weak var endDatePicker: UIDatePicker! // Lưu lại calendar User đã chọn var selectedCalendar: EKCalendar! @IBAction func saveEventActionButton(_ sender: AnyObject) { } |
Sau khi hoàn thành bước này, trở về với func addNewEvent ở EventViewController.swift ta sẽ thêm đoan code sau:
1 2 3 4 5 | func addNewEvent() { guard let newEventVC = self.storyboard?.instantiateViewController(withIdentifier: "newEventVC") as? NewEventViewController else {return} newEventVC.selectedCalendar = calendar self.navigationController?.pushViewController(newEventVC, animated: true) } |
OK, như vậy là chúng ta đã chuyển được calendar mà người dùng chọn để truyền sang NewEventViewController.swift. Tiếp theo, chúng ta sẽ thêm đoạn code sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | @IBAction func saveEventActionButton(_ sender: AnyObject) { // Khởi tạo eventStore: ? cái này quá quen thuộc r nhĩ let eventStore = EKEventStore() // Tạo calendar của event theo calendar đã chọn trước đó if let calendarForEvent = eventStore.calendar(withIdentifier: self.selectedCalendar.calendarIdentifier) { // Khởi tạo event mới từ eventstore let newEvent = EKEvent(eventStore: eventStore) // Gán lịch của event mới bằng với calendar mà chúng ta tạo được ở trên newEvent.calendar = calendarForEvent // Nếu User không nhập tên event thì mặc định sẽ là New Event newEvent.title = (self.eventNameTextField.text != nil && !self.eventNameTextField.text!.isEmpty) ? self.eventNameTextField.text! : "New Event" newEvent.startDate = self.startDatePicker.date newEvent.endDate = self.endDatePicker.date // Lưu Event lại bằng cách sử dụng eventStore do { try eventStore.save(newEvent, span: .thisEvent, commit: true) // Nếu add thành công, màn hình sẽ tự động trở về màn hình cha let _ = self.navigationController?.popViewController(animated: true) } catch { // Nếu có lỗi, hiển thị thông báo về lỗi nhận được let alert = UIAlertController(title: "Event could not save", message: (error as NSError).localizedDescription, preferredStyle: .alert) let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil) alert.addAction(OKAction) self.present(alert, animated: true, completion: nil) } } } |
Run lại và cảm nhận kết quả nhé!
Các bạn có thể download source code ở đây
Nguồn: IDE Academy tổng hợp