Advertisement
  1. Code
  2. CloudKit

Membuat Aplikasi Daftar Belanja dengan CloudKit: Menambahkan Catatan

Scroll to top
Read Time: 23 min
This post is part of a series called Building a Shopping List Application With CloudKit.
Building a Shopping List Application With CloudKit: Introduction
Building a Shopping List Application With CloudKit: Adding Relationships

Indonesian (Bahasa Indonesia) translation by Primrose (you can also view the original English article)

Dalam tutorial pertama dari seri ini, kita menjelajahi kerangka kerja dan infrastruktur CloudKit. Kit juga meletakkan dasar untuk aplikasi contoh yang akan kita bangun, aplikasi daftar belanja. Dalam tutorial ini, kita berfokus untuk menambahkan, mengedit, dan menghapus daftar belanja.

Prasyarat

Seperti yang saya sebutkan di tutorial sebelumnya, saya akan menggunakan Xcode 7 dan Swift 2. Jika Anda menggunakan Xcode versi lama, maka perlu diingat bahwa Anda menggunakan versi bahasa pemrograman Swift yang berbeda.

Dalam tutorial ini, kami akan terus bekerja dengan proyek yang kami buat di tutorial pertama. Anda dapat mengunduhnya dari GitHub.

1. Menyiapkan CocoaPods

Aplikasi daftar belanja akan menggunakan perpustakaan SVProgressHUD, perpustakaan populer yang dibuat oleh Sam Vermette yang membuatnya mudah untuk menampilkan indikator kemajuan. Anda dapat menambahkan perpustakaan secara manual ke proyek Anda, tetapi saya sangat menyarankan menggunakan CocoaPods untuk mengelola dependensi. Apakah Anda baru mengenal CocoaPods? Saya telah menulis tutorial pengantar untuk CocoaPods yang akan membantu Anda mendapatkan kecepatan.

Langkah 1: Membuat Podfile

Buka Finder dan arahkan ke akar proyek Xcode Anda. Buat file baru, beri nama Podfile, dan tambahkan baris Ruby berikut ini.

1
platform :ios, '8.0'
2
use_frameworks!
3
4
target 'Lists' do
5
  pod 'SVProgressHUD', '~> 1.1'
6
end

The first line specifies the platform, iOS, and the project's deployment target, iOS 8.0. Baris kedua penting jika Anda menggunakan Swift. Swift tidak mendukung pustaka statis, tetapi CocoaPods menyediakan opsi ini sejak versi 0,36 untuk menggunakan kerangka kerja. Kami kemudian menentukan dependensi untuk target Daftar proyek. Ganti Daftar dengan nama target Anda jika target Anda diberi nama berbeda.

Langkah 2: Memasang Ketergantungan

Buka Terminal, arahkan ke akar proyek Xcode Anda, dan jalankan pod install. Ini akan melakukan sejumlah hal untuk Anda, seperti menginstal dependensi yang ditentukan dalam Podfile dan membuat ruang kerja Xcode.

Setelah menyelesaikan pengaturan CocoaPods, tutup proyek dan buka ruang kerja CocoaPods yang dibuat untuk Anda. Yang terakhir ini sangat penting. Buka ruang kerja, bukan proyek. Ruang kerja mencakup dua proyek, proyek Daftar dan proyek bernama Pods.

2. Mendata Daftar Belanja

Langkah 1: Rumah tangga

Kami siap untuk memfokuskan kembali pada kerangka CloudKit. Namun, pertama-tama, kita perlu melakukan pembersihan dengan mengganti nama kelas ViewController ke kelas ListsViewController.

Mulailah dengan mengganti nama ViewController.swift ke ListsViewController.swift. Buka ListsViewController.swift dan ubah nama kelas ViewController ke kelas ListsViewController.

Selanjutnya, buka Main.storyboard, luaskan View Controller Scene dalam Outline Dokumen di sebelah kiri dan pilih View Controller. Buka  Identity Inspector di sebelah kanan dan ubah Class ke ListsViewController.

Langkah 2: Menambahkan tampilan tabel

Ketika pengguna membuka aplikasi, itu disajikan dengan daftar belanja mereka. Kami akan menampilkan daftar belanja dalam tampilan tabel. Mari kita mulai dengan mengatur antarmuka pengguna. Pilih Lists View Controller di Lists View Controller Scene dan pilih Embed In> Navigation Controller dari menu Editor Xcode.

Tambahkan tampilan tabel ke tampilan pengontrol tampilan dan buat batasan tata letak yang diperlukan untuknya. Dengan tampilan tabel yang dipilih, buka Attributes Inspector dan setel Prototype Cells ke 1. Pilih sel prototipe dan atur Style ke Basic dan Identifier ke ListCell.

Dengan tampilan tabel dipilih, buka Connections Inspector. Hubungkan data tampilan tabel dataSource dan delegate outlet ke Lists View Controller.

Langkah 3: Bagian Kosong

Meskipun kami hanya membuat contoh aplikasi untuk menggambarkan bagaimana CloudKit berfungsi, saya ingin menampilkan pesan jika ada yang salah atau jika tidak ada daftar belanja ditemukan di iCloud. Tambahkan label ke pengontrol tampilan, menjadikannya sebesar tampilan pengontrol tampilan, buat batasan tata letak yang diperlukan untuknya, dan pusatkan teks label.

Karena kita sedang berhadapan dengan permintaan jaringan, saya juga ingin menampilkan tampilan indikator aktivitas selama aplikasi menunggu tanggapan dari iCloud. Tambahkan tampilan indikator aktivitas ke tampilan pengontrol tampilan dan pusatkan dalam tampilan induknya. Di Attributes Inspector, centang kotak centang berlabel Hides When Stopped.

Lists View ControllerLists View ControllerLists View Controller

Langkah 4: Menghubungkan Outlet

Buka ListsViewController.swift dan nyatakan outlet untuk label, tampilan tabel, dan tampilan indikator aktivitas. Ini juga saat yang tepat untuk membuat kelas ListsViewController sesuai dengan protokol UITableViewDataSource dan UITableViewDelegate.

Perhatikan bahwa saya juga menambahkan pernyataan impor untuk kerangka kerja SVProgressHUD dan bahwa saya telah mendeklarasikan konstanta statis untuk pengenal penggunaan kembali sel prototipe yang kami buat di storyboard.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
6
    static let ListCell = "ListCell"
7
    
8
    @IBOutlet weak var messageLabel: UILabel!
9
    @IBOutlet weak var tableView: UITableView!
10
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
11
    
12
    ...
13
    
14
}

Kembali ke storyboard dan hubungkan outlet dengan pandangan yang terkait dalam Lists View Controller Scene.

Langkah 5: Mempersiapkan Tampilan Tabel

Sebelum kami mengambil data dari iCloud, kami perlu memastikan tampilan tabel siap untuk menampilkan data. Pertama-tama kita perlu membuat properti, lists, untuk menyimpan rekaman yang akan kita ambil. Ingat bahwa catatan adalah contoh kelas CKRecord Ini berarti properti yang akan menyimpan data dari iCloud adalah tipe [CKRecord], sebuah array dari instance CKRecord.

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
    static let ListCell = "ListCell"
3
    
4
    @IBOutlet weak var messageLabel: UILabel!
5
    @IBOutlet weak var tableView: UITableView!
6
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
7
    
8
    var lists = [CKRecord]()
9
    
10
    ...
11
    
12
}

Untuk memulai, kita perlu mengimplementasikan tiga metode protokol UITableViewDataSource:

  • numberOfSectionsInTableView(_:)
  • numberOfRowsInSection(_:)
  • cellForRowAtIndexPath(_:)

Jika Anda memiliki pengalaman bekerja dengan tampilan tabel, maka penerapan masing-masing metode ini sangat mudah. Namun, cellForRowAtIndexPath (_ :) mungkin memerlukan beberapa penjelasan. Ingat bahwa instance CKRecord adalah kamus pasangan nilai-kunci supercharged. Untuk mengakses nilai kunci tertentu, Anda memanggil objectForKey (_ :) pada objek CKRecord. Itulah yang kita lakukan di cellForRowAtIndexPath (_ :). Kami mengambil catatan yang sesuai dengan baris tampilan tabel dan menanyakannya untuk nilai "name" kunci. Jika pasangan kunci-nilai tidak ada, kami menampilkan tanda pisah untuk menunjukkan daftar belum memiliki nama.

1
// MARK: -

2
// MARK: Table View Data Source Methods

3
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
4
    return 1;
5
}
6
7
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
8
    return lists.count
9
}
10
11
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
12
    // Dequeue Reusable Cell

13
    let cell = tableView.dequeueReusableCellWithIdentifier(ListsViewController.ListCell, forIndexPath: indexPath)
14
    
15
    // Configure Cell

16
    cell.accessoryType = .DetailDisclosureButton
17
    
18
    // Fetch Record

19
    let list = lists[indexPath.row]
20
    
21
    if let listName = list.objectForKey("name") as? String {
22
        // Configure Cell

23
        cell.textLabel?.text = listName
24
        
25
    } else {
26
        cell.textLabel?.text = "-"
27
    }
28
    
29
    return cell
30
}

Langkah 6: Mempersiapkan Antarmuka Pengguna

Ada satu langkah lagi untuk kami ambil, siapkan antarmuka pengguna. Dalam metode viewDidLoad view controller, hapus panggilan metode fetchUserRecordID dan aktifkan setupView, metode helper.

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    setupView()
5
}

Metode setupView menyiapkan antarmuka pengguna untuk mengambil daftar catatan. Kami menyembunyikan label dan tampilan tabel, dan memberi tahu tampilan indikator aktivitas untuk mulai menjiwai.

1
// MARK: -

2
// MARK: View Methods

3
private func setupView() {
4
    tableView.hidden = true
5
    messageLabel.hidden = true
6
    activityIndicatorView.startAnimating()
7
}

Bangun dan jalankan aplikasi di perangkat atau di Simulator iOS. Jika Anda telah mengikuti langkah-langkah di atas, Anda akan melihat tampilan kosong dengan tampilan indikator aktivitas berputar di tengah.

Busy Pretending to Be Fetching DataBusy Pretending to Be Fetching DataBusy Pretending to Be Fetching Data

Langkah 7: Membuat Jenis Rekaman

Sebelum kami mengambil catatan apa pun, kami perlu membuat jenis catatan untuk daftar belanja di dasbor CloudKit. Dasbor CloudKit adalah aplikasi web yang memungkinkan pengembang mengelola data yang disimpan di server iCloud Apple

Pilih proyek di Project Navigator dan pilih target List dari daftar target. Buka tab Capabilities di bagian atas dan rentangkan bagian iCloud. Di bawah daftar penampung iCloud, klik tombol yang berlabel CloudKit Dashboard.

Open CloudKit DashboardOpen CloudKit DashboardOpen CloudKit Dashboard

Masuk dengan akun pengembang Anda dan pastikan aplikasi Lists dipilih di kiri atas. Di sebelah kiri, pilih Record Types dari bagian Schema Setiap aplikasi secara default memiliki tipe catatan Pengguna. Untuk membuat tipe catatan baru, klik tombol plus di bagian atas kolom ketiga. Kami akan mengikuti konvensi penamaan Apple dan menamai Lists jenis rekaman, bukan Lists.

Adding a New Record TypeAdding a New Record TypeAdding a New Record Type

Perhatikan bahwa bidang pertama secara otomatis dibuat untuk Anda. Atur Field Name untuk name dan verifikasi bahwa Field Type diatur ke String. Jangan lupa klik tombol Save di bagian bawah untuk membuat tipe catatan Lists. Kami akan mengunjungi kembali Dasbor CloudKit nanti dalam seri ini.

Langkah 8: Melakukan Query

Dengan jenis catatan Lists dibuat, akhirnya waktu untuk mengambil beberapa catatan dari iCloud. Kerangka CloudKit menyediakan dua API untuk berinteraksi dengan iCloud, API kenyamanan, dan API berdasarkan kelas NSOperation. Kita akan menggunakan kedua API dalam seri ini, tetapi kami akan membuatnya tetap sederhana untuk saat ini dan menggunakan API kenyamanan.

Di Xcode, buka ListsViewController.swift dan aktifkan metode fetchLists di viewDidLoad. Metode fetchLists adalah metode pembantu lain. Mari kita lihat penerapan metode.

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    setupView()
5
    fetchLists()
6
}

Karena catatan daftar belanja disimpan dalam basis data pribadi pengguna, pertama-tama kita mendapatkan referensi ke basis data pribadi penampung default. Untuk mengambil daftar belanja pengguna, kita perlu melakukan kueri pada basis data pribadi, menggunakan kelas CKQuery.

1
// MARK: -

2
// MARK: Helper Methods

3
private func fetchLists() {
4
    // Fetch Private Database

5
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
6
    
7
    // Initialize Query

8
    let query = CKQuery(recordType: RecordTypeLists, predicate: NSPredicate(format: "TRUEPREDICATE"))
9
    
10
    // Configure Query

11
    query.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
12
    
13
    // Perform Query

14
    privateDatabase.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
15
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
16
            // Process Response on Main Thread

17
            self.processResponseForQuery(records, error: error)
18
        })
19
    }
20
}

Kami menginisialisasi turunan CKQuery dengan menjalankan init (recordType: predicate :) initializer yang ditunjuk, meneruskan tipe record dan objek NSPredicate. Untuk menghindari salah ketik, saya telah membuat konstanta untuk jenis rekaman. Konstanta, RecordTypeLists, dideklarasikan di bagian atas ListsViewController.swift.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
let RecordTypeLists = "Lists"
6
7
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
8
...

Sebelum kami mengeksekusi query, kami mengatur properti query sortDescriptors Kami membuat larik yang berisi objek NSSortDescriptor dengan "name" kunci dan set menaik ke true.

Menjalankan kueri sederhana seperti memanggil performQuery (_: inZoneWithID: completionHandler :) pada privateDatabase, meneruskan query sebagai argumen pertama. Parameter kedua menentukan identifier dari zona rekaman tempat kueri akan dilakukan. Dengan melewatkan nil, query dilakukan pada zona default dari database.

Argumen ketiga adalah penangan penyelesaian. Penutupan menerima array opsional objek CKRecord dan instance NSError opsional. Dokumentasi CloudKit secara eksplisit menyebutkan bahwa handler penyelesaian dapat dipanggil dari sembarang utas. Oleh karena itu kami mengirimkan pemrosesan tanggapan ke utas utama dengan membungkus prosesResponseForQuery (_: error :) metode panggilan dalam penutupan dispatch_async. Begitulah cara mudahnya melakukan kueri. Mari kita lihat bagaimana kami menangani respons kueri.

Langkah 9: Memproses Respons

Implementasi processResponseForQuery (_: error :) tidak sulit jika Anda terbiasa dengan bahasa Swift. Kami memeriksa isi catatan dan parameter kesalahan, dan memperbarui variabel pesan yang sesuai.

1
private func processResponseForQuery(records: [CKRecord]?, error: NSError?) {
2
    var message = ""
3
    
4
    if let error = error {
5
        print(error)
6
        message = "Error Fetching Records"
7
        
8
    } else if let records = records {
9
        lists = records
10
        
11
        if lists.count == 0 {
12
            message = "No Records Found"
13
        }
14
        
15
    } else {
16
        message = "No Records Found"
17
    }
18
    
19
    if message.isEmpty {
20
        tableView.reloadData()
21
    } else {
22
        messageLabel.text = message
23
    }
24
    
25
    updateView()
26
}

Di akhir metode, kami menjalankan updateView. Dalam metode pembantu ini, kami memperbarui antarmuka pengguna berdasarkan konten properti lists.

1
private func updateView() {
2
    let hasRecords = lists.count > 0
3
    
4
    tableView.hidden = !hasRecords
5
    messageLabel.hidden = hasRecords
6
    activityIndicatorView.stopAnimating()
7
}

Bangun dan jalankan aplikasi untuk menguji apa yang sudah kami dapatkan sejauh ini. Saat ini kami tidak memiliki catatan, tetapi kami akan memperbaikinya di bagian selanjutnya dari tutorial ini.

No Records FoundNo Records FoundNo Records Found

3. Menambahkan Daftar Belanja

Langkah 1: Membuat Kelas AddListViewController

Karena menambahkan dan mengedit daftar belanja sangat mirip, kami akan menerapkan keduanya pada saat yang bersamaan. Buat file baru dan beri nama AddListViewController.swift. Buka file yang baru dibuat dan buat subclass UIViewController bernama AddListViewController. Di bagian atas, tambahkan pernyataan impor untuk kerangka UIKit, CloudKit, dan SVProgressHUD. Deklarasikan dua outlet, salah satu tipe UITextField! dan salah satu dari jenis UIBarButtonItem !. Yanga tidak kalah penting, buat dua tindakan, cancel (_ :) dan save (_ :).

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
class AddListViewController: UIViewController {
6
    
7
    @IBOutlet weak var nameTextField: UITextField!
8
    @IBOutlet weak var saveButton: UIBarButtonItem!
9
    
10
    @IBAction func cancel(sender: AnyObject) {
11
        
12
    }
13
    
14
    @IBAction func save(sender: AnyObject) {
15
        
16
    }
17
    
18
}

Langkah 2: Membuat Antarmuka Pengguna

Buka Main.storyboard dan tambahkan pengontrol tampilan ke storyboard. Dengan pengontrol tampilan dipilih, buka Identity Inspector di sebelah kanan dan atur Class ke AddListViewController

Pengguna akan dapat menavigasi ke pengontrol tampilan daftar add dengan mengetuk tombol di pengontrol tampilan daftar. Seret item tombol bar dari Object Library ke bilah navigasi dari lists view controller. Dengan item tombol bar dipilih, buka  Attributes Inspector dan setel System Item ke Add. Tekan Control dan seret dari item tombol bar ke pengontrol tampilan daftar add dan pilih push dari menu yang muncul.

Sangat penting bahwa Anda memilih push dari bagian Action Segue. Ini harus menjadi item pertama di menu. Pilih segue yang Anda buat dan atur Identifier ke ListDetail di Attributes Inspector di sebelah kanan.

Tambahkan dua item tombol bar ke bilah navigasi dari pengontrol tampilan daftar add, satu di kiri dan satu di sebelah kanan. Setel Item Sistem dari item tombol bilah kiri menjadi Cancel dan tombol pilihan tombol kanan ke Save. Akhirnya, tambahkan kolom teks ke controller tampilan daftar add. Pusatkan bidang teks dan atur Alignment-nya ke pusat di Attributes Inspector.

Add List View ControllerAdd List View ControllerAdd List View Controller

Ada kemungkinan bahwa, setelah membuat segue dari pengendali tampilan daftar ke pengontrol tampilan daftar tambahan, tidak ada item navigasi yang dibuat untuk Add List View Controller Scene. Saya yakin ini adalah bug di Xcode 7.

Untuk mengatasi masalah ini, pilih segue dan, di Attributes Inspector, tetapkan Segue ke Deprecated Segues > Push. Ini akan menambahkan item navigasi ke TKP. Selanjutnya, atur Segue kembali ke Adaptive Segues> Show.

Akhirnya, hubungkan outlet dan tindakan yang Anda buat di AddListViewController.swift ke elemen antarmuka pengguna yang sesuai di layar.

Langkah 3: Protokol AddListViewControllerDelegate

Sebelum kita mengimplementasikan kelas AddListViewController, kita perlu mendeklarasikan protokol yang akan kita gunakan untuk berkomunikasi dari pengontrol tampilan daftar add ke pengendali tampilan daftar. Protokol mendefinisikan dua metode, satu untuk menambahkan dan satu untuk memperbarui daftar belanja. Seperti inilah protokol itu.

1
protocol AddListViewControllerDelegate {
2
    func controller(controller: AddListViewController, didAddList list: CKRecord)
3
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
4
}

Kami juga perlu menyatakan tiga properti, satu untuk delegasi, satu untuk daftar belanja yang dibuat atau diperbarui, dan variabel pembantu yang menunjukkan apakah kami membuat daftar belanja baru atau mengedit catatan yang sudah ada.

1
import UIKit
2
import CloudKit
3
import SVProgressHUD
4
5
protocol AddListViewControllerDelegate {
6
    func controller(controller: AddListViewController, didAddList list: CKRecord)
7
    func controller(controller: AddListViewController, didUpdateList list: CKRecord)
8
}
9
10
class AddListViewController: UIViewController {
11
    
12
    @IBOutlet weak var nameTextField: UITextField!
13
    @IBOutlet weak var saveButton: UIBarButtonItem!
14
    
15
    var delegate: AddListViewControllerDelegate?
16
    var newList: Bool = true
17
    
18
    var list: CKRecord?
19
    
20
    @IBAction func cancel(sender: AnyObject) {
21
        
22
    }
23
    
24
    @IBAction func save(sender: AnyObject) {
25
        
26
    }
27
    
28
}

Implementasi kelas AddListViewController bukanlah ilmu roket. Metode yang terkait dengan siklus hidup tampilan adalah singkat dan mudah dimengerti. Di viewDidLoad, pertama kita memanggil metode penolong setupView. Kami akan menerapkan metode ini sebentar lagi. Kami kemudian memperbarui nilai variabel helper newList berdasarkan nilai properti lists. Jika lists sama dengan nil, maka kita tahu bahwa kita sedang membuat catatan baru. Di viewDidLoad, kami juga menambahkan pengontrol tampilan sebagai pengamat untuk pemberitahuan UITextFieldTextDidChangeNotification.

1
// MARK: -

2
// MARK: View Life Cycle

3
override func viewDidLoad() {
4
    super.viewDidLoad()
5
    
6
    setupView()
7
    
8
    // Update Helper

9
    newList = list == nil
10
    
11
    // Add Observer

12
    let notificationCenter = NSNotificationCenter.defaultCenter()
13
    notificationCenter.addObserver(self, selector: "textFieldTextDidChange:", name: UITextFieldTextDidChangeNotification, object: nameTextField)
14
}
15
16
override func viewDidAppear(animated: Bool) {
17
    nameTextField.becomeFirstResponder()
18
}

Di viewDidAppear (_ :), kita memanggil becomeFirstResponder di bidang teks untuk menyajikan keyboard kepada pengguna.

Di setupView, kita memanggil dua metode pembantu, updateNameTextField dan updateSaveButton. Di updateNameTextField, kami mengisi bidang teks jika lists tidak nil. Dengan kata lain, jika kita mengedit rekaman yang sudah ada, maka kita mengisi bidang teks dengan nama rekaman itu.

Metode updateSaveButton bertugas mengaktifkan dan menonaktifkan item tombol bar di kanan atas. Kita hanya mengaktifkan tombol simpan jika nama daftar belanja bukan string kosong.

1
// MARK: -

2
// MARK: View Methods

3
private func setupView() {
4
    updateNameTextField()
5
    updateSaveButton()
6
}
7
8
// MARK: -

9
private func updateNameTextField() {
10
    if let name = list?.objectForKey("name") as? String {
11
        nameTextField.text = name
12
    }
13
}
14
15
// MARK: -

16
private func updateSaveButton() {
17
    let text = nameTextField.text
18
    
19
    if let name = text {
20
        saveButton.enabled = !name.isEmpty
21
    } else {
22
        saveButton.enabled = false
23
    }
24
}

Langkah 4: Menerapkan Tindakan

cancel(_:) tindakan sesederhana yang didapatkannya. Kita memunculkan pengontrol tampilan atas dari susunan navigasi. Save (_ :) action lebih menarik. Dalam metode ini, kita mengekstrak input pengguna dari kolom teks dan mendapatkan referensi ke basis data pribadi kontainer standar.

Jika kita menambahkan daftar belanja baru, maka kita membuat contoh CKRecord baru dengan memohon init (recordType :), meneruskan RecordTypeLists sebagai tipe catatan. Kami kemudian memperbarui nama daftar belanja dengan menetapkan nilai catatan untuk "name" kunci.

Karena menyimpan catatan melibatkan permintaan jaringan dan dapat mengambil jumlah waktu yang tidak sepele, kami menunjukkan indikator kemajuan. Untuk menyimpan catatan baru atau perubahan apa pun pada rekaman yang sudah ada, kami memanggil saveRecord (_: completionHandler :) pada privateDatabase, meneruskan catatan sebagai argumen pertama. Argumen kedua adalah penangan penyelesaian lain yang dipanggil saat menyimpan catatan selesai, berhasil atau tidak berhasil.

Handler penyelesaian menerima dua argumen, CKRecord opsional dan NSError opsional. Seperti yang saya sebutkan sebelumnya, handler penyelesaian dapat dipanggil pada sembarang thread, yang berarti kita perlu mengkodekannya. Kami melakukan ini dengan secara eksplisit menerapkan metode processResponse (_: error :) pada utas utama.

1
// MARK: -

2
// MARK: Actions

3
@IBAction func cancel(sender: AnyObject) {
4
    navigationController?.popViewControllerAnimated(true)
5
}
6
7
@IBAction func save(sender: AnyObject) {
8
    // Helpers

9
    let name = nameTextField.text
10
    
11
    // Fetch Private Database

12
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
13
    
14
    if list == nil {
15
        list = CKRecord(recordType: RecordTypeLists)
16
    }
17
    
18
    // Configure Record

19
    list?.setObject(name, forKey: "name")
20
    
21
    // Show Progress HUD

22
    SVProgressHUD.show()
23
    
24
    // Save Record

25
    privateDatabase.saveRecord(list!) { (record, error) -> Void in
26
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
27
            // Dismiss Progress HUD

28
            SVProgressHUD.dismiss()
29
            
30
            // Process Response

31
            self.processResponse(record, error: error)
32
        })
33
    }
34
}

Dalam processResponse (_: error :), kami memverifikasi jika kesalahan dilemparkan. Jika kami mengalami masalah, kami menampilkan peringatan kepada pengguna. Jika semuanya berjalan lancar, kami memberi tahu delegasi dan menampilkan pengontrol tampilan dari tumpukan navigasi.

1
// MARK: -

2
// MARK: Helper Methods

3
private func processResponse(record: CKRecord?, error: NSError?) {
4
    var message = ""
5
    
6
    if let error = error {
7
        print(error)
8
        message = "We were not able to save your list."
9
        
10
    } else if record == nil {
11
        message = "We were not able to save your list."
12
    }
13
    
14
    if !message.isEmpty {
15
        // Initialize Alert Controller

16
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
17
        
18
        // Present Alert Controller

19
        presentViewController(alertController, animated: true, completion: nil)
20
        
21
    } else {
22
        // Notify Delegate

23
        if newList {
24
            delegate?.controller(self, didAddList: list!)
25
        } else {
26
            delegate?.controller(self, didUpdateList: list!)
27
        }
28
        
29
        // Pop View Controller

30
        navigationController?.popViewControllerAnimated(true)
31
    }
32
}

Yang tidak kalah penting, ketika pengontrol tampilan menerima pemberitahuan UITextFieldTextDidChangeNotification, itu akan memanggil updateSaveButton untuk memperbarui tombol simpan.

1
// MARK: -

2
// MARK: Notification Handling

3
func textFieldTextDidChange(notification: NSNotification) {
4
    updateSaveButton()
5
}

Langkah 5: Mengikat Semuanya Bersama

Di kelas ListsViewController, kita masih perlu memperhatikan beberapa hal. Mari kita mulai dengan menyesuaikan kelas ke protokol AddListViewControllerDelegate.

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
...

Ini juga berarti bahwa kita perlu menerapkan metode protokol AddListViewControllerDelegate. Dalam metode controller (_: didAddList :), kami menambahkan catatan baru ke array objek CKRecord. Kita kemudian mengurutkan berbagai catatan, memuat ulang tampilan tabel, dan menjalankan updateView pada pengontrol tampilan.

1
// MARK: -

2
// MARK: Add List View Controller Delegate Methods

3
func controller(controller: AddListViewController, didAddList list: CKRecord) {
4
    // Add List to Lists

5
    lists.append(list)
6
    
7
    // Sort Lists

8
    sortLists()
9
    
10
    // Update Table View

11
    tableView.reloadData()
12
13
    // Update View

14
    updateView()
15
}

Metode sortLists cukup mendasar. Kami memanggil sortInPlace pada larik rekaman, menyortir larik berdasarkan nama rekaman.

1
private func sortLists() {
2
    lists.sortInPlace {
3
        var result = false
4
        let name0 = $0.objectForKey("name") as? String
5
        let name1 = $1.objectForKey("name") as? String
6
        
7
        if let listName0 = name0, listName1 = name1 {
8
            result = listName0.localizedCaseInsensitiveCompare(listName1) == .OrderedAscending
9
        }
10
        
11
        return result
12
    }
13
}

Implementasi metode kedua dari protokol AddListViewControllerDelegate, controller (_: didUpdateList :), terlihat hampir identik. Karena kita tidak menambahkan catatan, kita hanya perlu mengurutkan berbagai rekaman dan memuat ulang tampilan tabel. Tidak perlu memanggil updateView pada pengontrol tampilan karena larik rekaman secara definisi tidak kosong.

1
func controller(controller: AddListViewController, didUpdateList list: CKRecord) {
2
    // Sort Lists

3
    sortLists()
4
    
5
    // Update Table View

6
    tableView.reloadData()
7
}

Untuk mengedit rekaman, pengguna perlu mengetuk tombol aksesori pada baris tampilan tabel. Ini berarti bahwa kita perlu menerapkan metode tableView (_: accessoryButtonTappedForRowWithIndexPath :) dari protokol UITableViewDelegate. Sebelum kita menerapkan metode ini, nyatakan properti pembantu, selection, untuk menyimpan pilihan pengguna

1
class ListsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, AddListViewControllerDelegate {
2
    static let ListCell = "ListCell"
3
    
4
    @IBOutlet weak var messageLabel: UILabel!
5
    @IBOutlet weak var tableView: UITableView!
6
    @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
7
    
8
    var lists = [CKRecord]()
9
    
10
    var selection: Int?
11
    
12
    ...
13
    
14
}

Di tableView (_: accessoryButtonTappedForRowWithIndexPath :), kami menyimpan pilihan pengguna dalam selection dan memberi tahu pengontrol tampilan untuk melakukan segue yang mengarah ke pengontrol tampilan daftar add. Jika Anda penasaran, saya telah membuat konstanta dalam ListsViewController.swift untuk pengenal segue, SegueListDetail.

1
// MARK: -

2
// MARK: Table View Delegate Methods

3
func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
4
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
5
    
6
    // Save Selection

7
    selection = indexPath.row
8
    
9
    // Perform Segue

10
    performSegueWithIdentifier(SegueListDetail, sender: self)
11
}

Kita hampir sampai. Ketika segue dengan identifier ListDetail dilakukan, kita perlu mengkonfigurasi contoh AddListViewController yang didorong ke susunan navigasi. Kita melakukan ini di prepareForSegue (_: sender :?).

Segue memberikan referensi kepada pengendali tampilan tujuan, contoh AddListViewController. Kita mengatur properti delegate, dan, jika daftar belanja diperbarui, kami mengatur properti lists pengendali tampilan ke catatan yang dipilih.

1
// MARK: -

2
// MARK: Segue Life Cycle

3
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
4
    guard let identifier = segue.identifier else { return }
5
    
6
    switch identifier {
7
    case SegueListDetail:
8
        // Fetch Destination View Controller

9
        let addListViewController = segue.destinationViewController as! AddListViewController
10
        
11
        // Configure View Controller

12
        addListViewController.delegate = self
13
        
14
        if let selection = selection {
15
            // Fetch List

16
            let list = lists[selection]
17
            
18
            // Configure View Controller

19
            addListViewController.list = list
20
        }
21
    default:
22
        break
23
    }
24
}

Bangun dan jalankan aplikasi untuk melihat hasilnya. Anda sekarang dapat menambahkan daftar belanja baru dan mengedit nama daftar belanja yang ada.

4. Menghapus Daftar Belanja

Menambahkan kemampuan untuk menghapus daftar belanja tidak banyak pekerjaan ekstra. Pengguna harus dapat menghapus daftar belanja dengan menggesek baris tampilan tabel dari kanan ke kiri dan mengetuk tombol hapus yang terungkap. Untuk memungkinkan hal ini, kita perlu menerapkan dua metode lagi dari protokol UITableViewDataSource:

  • tableView (_: canEditRowAtIndexPath :)
  • tableView(_:commitEditingStyle:forRowAtIndexPath:)

Implementasi tableView (_: canEditRowAtIndexPath :) sepele seperti yang Anda lihat di bawah ini.

1
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
2
    return true
3
}

Dalam tableView (_: commitEditingStyle: forRowAtIndexPath :), kami mengambil catatan yang benar dari larik rekaman dan menjalankan deleteRecord (_ :) pada pengontrol tampilan, meneruskan catatan yang perlu dihapus.

1
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
2
    guard editingStyle == .Delete else { return }
3
    
4
    // Fetch Record

5
    let list = lists[indexPath.row]
6
    
7
    // Delete Record

8
    deleteRecord(list)
9
}

Metode deleteRecord (_ :) akan terlihat akrab sekarang. Kami menunjukkan indikator progres dan memanggil deleteRecordWithID (_: completionHandler :) pada basis data pribadi kontainer standar. Perhatikan bahwa kita melewati pengenal catatan, bukan catatan itu sendiri. Handler penyelesaian menerima dua argumen, opsional CKRecordID dan NSError opsional.

1
private func deleteRecord(list: CKRecord) {
2
    // Fetch Private Database

3
    let privateDatabase = CKContainer.defaultContainer().privateCloudDatabase
4
    
5
    // Show Progress HUD

6
    SVProgressHUD.show()
7
    
8
    // Delete List

9
    privateDatabase.deleteRecordWithID(list.recordID) { (recordID, error) -> Void in
10
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
11
            // Dismiss Progress HUD

12
            SVProgressHUD.dismiss()
13
            
14
            // Process Response

15
            self.processResponseForDeleteRequest(list, recordID: recordID, error: error)
16
        })
17
    }
18
}

Dalam handler penyelesaian, kami mengabaikan indikator progres dan memanggil processResponseForDeleteRequest (_: recordID: error :) pada utas utama. Dalam metode ini, kami memeriksa nilai-nilai recordID dan error yang telah diberikan CloudKit API kepada kami dan kami memperbarui message yang sesuai. Jika permintaan penghapusan berhasil, maka kami memperbarui antarmuka pengguna dan larik rekaman.

1
private func processResponseForDeleteRequest(record: CKRecord, recordID: CKRecordID?, error: NSError?) {
2
    var message = ""
3
    
4
    if let error = error {
5
        print(error)
6
        message = "We are unable to delete the list."
7
        
8
    } else if recordID == nil {
9
        message = "We are unable to delete the list."
10
    }
11
    
12
    if message.isEmpty {
13
        // Calculate Row Index

14
        let index = lists.indexOf(record)
15
        
16
        if let index = index {
17
            // Update Data Source

18
            lists.removeAtIndex(index)
19
            
20
            if lists.count > 0 {
21
                // Update Table View

22
                tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Right)
23
                
24
            } else {
25
                // Update Message Label

26
                messageLabel.text = "No Records Found"
27
                
28
                // Update View

29
                updateView()
30
            }
31
        }
32
        
33
    } else {
34
        // Initialize Alert Controller

35
        let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
36
        
37
        // Present Alert Controller

38
        presentViewController(alertController, animated: true, completion: nil)
39
    }
40
}

Itu dia Saatnya menguji aplikasi dengan benar dengan beberapa data. Jalankan aplikasi di perangkat atau di Simulator iOS dan tambahkan beberapa daftar belanja. Anda harus bisa menambahkan, mengedit, dan menghapus daftar belanja.

Kesimpulan

Meskipun artikel ini cukup panjang, perlu diingat bahwa kami hanya sebentar berinteraksi dengan API CloudKit. API kenyamanan dari framework CloudKit ringan dan mudah digunakan.

Tutorial ini, bagaimanapun, juga menggambarkan bahwa pekerjaan Anda sebagai pengembang tidak terbatas untuk berinteraksi dengan API CloudKit. Penting untuk menangani kesalahan, menunjukkan kepada pengguna saat permintaan sedang berlangsung, memperbarui antarmuka pengguna, dan memberi tahu pengguna apa yang sedang terjadi.

Dalam artikel berikutnya dari seri ini, kami melihat lebih dekat hubungan dengan menambahkan kemampuan untuk mengisi daftar belanja dengan barang-barang. An empty shopping list isn't of much use and it certainly isn't fun. Tinggalkan pertanyaan apa pun yang Anda miliki di komentar di bawah atau hubungi saya di Twitter.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.