PappaGee PappaGee - 6 months ago 105
iOS Question

CKQuery, how to fetch all Fields from Record?

I have worked out how to search my CKrecord and display the string from one field only with the code below.

I have a few questions..( i am new to this )


  1. can i search and it not be case sensitive?

  2. How can I search ALL fields in the record and then display all of the fields on a match?



I am making a reference app, so you can search, for example, BBC1, and all things that relate to BBC1 (i.e. in that recordID) display.

Dashboard.jpg showing Record and Fields

import UIKit
import CloudKit
import MobileCoreServices

class ViewController: UIViewController {

@IBOutlet var addressField: UITextField!
@IBOutlet var commentsField: UITextView!

let container = CKContainer.defaultContainer()

var publicDatabase: CKDatabase?
var currentRecord: CKRecord?

override func viewDidLoad() {
publicDatabase = container.publicCloudDatabase
super.viewDidLoad()
}


@IBAction func performQuery(sender: AnyObject) {
let predicate = NSPredicate(format: "serviceName = %@", addressField.text!)
let query = CKQuery(recordType: "SystemFields", predicate: predicate)
publicDatabase?.performQuery(query, inZoneWithID: nil, completionHandler: ({results, error in
if(error != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error!.localizedDescription)
}
} else {
if results!.count > 0 {
let record = results![0]
self.currentRecord = record

dispatch_async(dispatch_get_main_queue()) {
self.commentsField.text = record.objectForKey("Location") as! String
}
} else { dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
}))
}

func notifyUser(title: String, message: String) -> Void
{
let alert = UIAlertController(title: title,
message: message,
preferredStyle: UIAlertControllerStyle.Alert)

let cancelAction = UIAlertAction(title: "OK",
style: .Cancel, handler: nil)
alert.addAction(cancelAction)
self.presentViewController(alert, animated: true,
completion: nil)
}
}


Print Results Output

Answer

According to Apple's CKQuery Class Reference, you should be able to do both by using a tokenized search of the record's fields:

To perform a tokenized search of a record’s fields, use the special operator self. A tokenized search searches any fields that have full-text search enabled, which is all string-based fields by default. Each distinct word in the tokenized string is treated as a separate token for the purpose of searching. Comparisons are case- and diacritic-insensitive. These token strings may be found in a single field or in multiple fields.

And here is the Listing 5 example (in Swift):

Listing 5 - Matching a field containing one or more tokens

var predicate = NSPredicate(format: "self contains 'bob smith'")

And the Listing 6 example (in Swift):

Listing 6 - Matching a field containing multiple tokens

var predicate = NSPredicate(format: "self contains 'bob' AND self contains 'smith'")

Finally, review the information on Indexes and Full-Text Search, as your record configuration in CloudKit affects which fields are searched (and performance).


EDIT: Further clarification for your use case:

Since you're using the contents of addressField.text to perform your search...

You have to decide whether you want a match to be:

Option 1 - When any single word in addressField.text matches any word in any field in a record.

In Swift 2.2:

let predicate = NSPredicate(format: "self contains %@", addressField.text!)

Option 2 - When all words in addressField.text are in a field in a record (although not necessarily in the same order).

In Swift 2.2:

// creates a compound predicate to match all words in a string
// returns: nil if input was empty, otherwise an NSPredicate
func createPredicateForMatchingAllWordsIn(string: String) -> NSPredicate?
{
    guard !string.isEmpty else { return nil }
    var predicateList : [NSPredicate] = []

    let words = string.componentsSeparatedByString(" ")
    for word in words {
        if !word.isEmpty {
            predicateList.append(NSPredicate(format: "self contains %@", word))
        }
    }

    return NSCompoundPredicate(andPredicateWithSubpredicates: predicateList)
}

let predicate = createPredicateForMatchingAllWordsIn(addressField.text!)

If you'd like to do additional filtering, like only showing records where the words matched in-order, you can then do it client-side. (The CKQuery class supports only a subset of the predicate behaviors offered by the full NSPredicate class.)