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.



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.



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.



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.



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.



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.



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.