1

I wonder that how creating Firebase matchesRegex query and listing current results using shouldChangeTextInRange.

I have did it before in Parse for searching username or user fullname in Parse Cloud. If someone shed light my path to do it in Firebase, it would be great.

// search updated
func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {

    // find by username
    let usernameQuery = PFQuery(className: "_User")
    usernameQuery.whereKey("username", matchesRegex: "(?i)" + searchBar.text!)
    usernameQuery.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error:NSError?) -> Void in

        if error == nil {

            // if no objects are found according to entered text in username column, find by fullname
            if objects!.isEmpty {

                let fullnameQuery = PFUser.query()
                fullnameQuery?.whereKey("fullname", matchesRegex: "(?i)" + self.searchBar.text!)
                fullnameQuery?.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in

                    if error == nil {

                        // clean up
                        self.usernameArray.removeAll(keepCapacity: false)
                        self.avaArray.removeAll(keepCapacity: false)

                        // found related objects
                        for object in objects! {
                            self.usernameArray.append(object.objectForKey("username") as! String)
                            self.avaArray.append(object.objectForKey("avatar") as! PFFile)
                        }

                        // reload
                        self.tableView.reloadData()

                    }

                })

            }

        }

        // clean up
        self.usernameArray.removeAll(keepCapacity: false)
        self.avaArray.removeAll(keepCapacity: false)

        // found related objects
        for object in objects! {
            self.usernameArray.append(object.objectForKey("username") as! String)
            self.avaArray.append(object.objectForKey("avatar") as! PFFile)
        }

        // reload
        self.tableView.reloadData()

    }

    return true

}

Update 1:

I couldn't do it using same parent name, snap.value didn't give me response, even not compile. So I have tried to do two separate parent node like that: firebase screen shot link

I'm getting indexOn security warning for each letter stroke first_name subnodes on xcode console:

[Firebase] Using an unspecified index. Consider adding ".indexOn": "first_name/T" at /people_spell to your security rules for better performance

[Firebase] Using an unspecified index. Consider adding ".indexOn": "first_name/Te" at /people_spell to your security rules for better performance

... much more times

I have tried these security rules it didn't solve these problems. It doesn't encompass each subnodes. How Can I provide this?

"people_spell": {
      ".read": "auth !== null",
      ".write": "auth !== null",
      ".indexOn": "first_name"
    },
"people": {
      ".read": "auth !== null",
      ".write": "auth !== null",
      ".indexOn": "first_name"
}

I'm using these lines of codes:

let peopleSpellRef = Firebase(url: "https://sampleproject.firebaseio.com/people_spell")
let peopleRef = Firebase(url: "https://sampleproject.firebaseio.com/people")

func searchBar(searchBar: UISearchBar, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {

   self.usernameArray.removeAll(keepCapacity: false)

   peopleSpellRef.queryOrderedByChild("first_name/\(searchBar.text!)").queryEqualToValue(true)
    .observeEventType(.Value, withBlock: { snapshot in

        let enumerator = snapshot.children
        while let rest = enumerator.nextObject() as? FDataSnapshot {

            peopleRef.childByAppendingPath("\(rest.key)").observeEventType(.Value, withBlock: { snapshot in

                self.usernameArray.removeAll(keepCapacity: false)

                let str: String = (snapshot.value.objectForKey("first_name")) as! (String)
                print(str)
                self.usernameArray.append(str)
                self.userkeyArray.append(snapshot.key)

                self.tableView.reloadData()
            })
        }


        self.usernameArray.removeAll(keepCapacity: false)
        self.tableView.reloadData()

    })

return true
}
iamburak
  • 3,508
  • 4
  • 34
  • 65

1 Answers1

2

This is not how Firebase works and you can't directly do a 'real time search' in Firebase for a substring. But, you can format your Firebase data to be searched and there are a couple of other methods which will also accomplish the goal.

One is straightforward: load your data from Firebase into an array and NSPredicate search (or other methods) through the array.

Another is that query'ing for full strings is easy; as long as the text is broken up into the chunks you want to search for:

people
  person_0
    first_name: "Ted"
    last_name: "Stryker"
    position: "sitting down and facing front"
  person_1
    first_name: "Teddy"

You can search for 'Ted' in first_name or 'Stryker' in last_name but you cannot search for 'front' in position.

Now the workaround and keep in mind disk space is cheap.

A node can be created that will let you search for stuff in a variety of ways

people
  person_0
   first_name //First name is Ted from above
    T: true
    Te: true
    Ted: true
    ed: true
    d: true
    e: true
  person_1
   first_name  //First name is Teddy from above
    T: true
    Te: true
    Ted: true
    etc

With this structure we can find all first names that start with Ted

    peopleRef.queryOrderedByChild("first_name/Ted").queryEqualToValue(true)
         .observeEventType(.Value, withBlock: { snapshot in

        for child in snapshot.children {
          print(child.value)  
        }
    })

Other searches can be performed; searching for all first names that contain e or Te etc.

If this is purely a situation where you are searching to perform an autofill in a text field (for example) pinging firebase on a keystroke by keystroke basis isn't going to work well.

Loading the data from Firebase into an array, or dictionary (or whatever) will probably be your best solution.

Jay
  • 34,438
  • 18
  • 52
  • 81
  • Thanks for your detailed answer. I have just tried to do it, it works but I'm getting indexOn security warning for each 'first_name' subnodes on xcode console. Please look at question again, updated. – iamburak Apr 27 '16 at 10:06
  • 1
    @tobeiosdev OK, first thing. I am again going to recommend NOT doing these kinds of queries in Firebase if you are using it in an autofill situation. You are MUCH better off loading the data into an array and search that. In response to your comment, I would read two things: [Firebase indexing](https://www.firebase.com/docs/security/guide/indexing-data.html) and then see Tom's answer to this deep indexing question [Using .indexOn with nested keys in Firebase](http://stackoverflow.com/questions/27179094/using-indexon-with-nested-keys-in-firebase) – Jay Apr 27 '16 at 13:22
  • I understand your advice and rearrange it as off loading for my autofill case. Appreciate you again, approve as answer for this question. In addition, I want to ask you another firebase question, I have encountered with a similar topic but it doesn't clear. I'm waiting an answer for a long time, please enlighten me at the appropriate time. http://stackoverflow.com/questions/36505160/how-implementing-firebase-pagination-in-swift – iamburak Apr 27 '16 at 19:25
  • Hi again, I want to ask about off-loading users. If there are many users, getting all of them into an array, It can expand memory usage and waiting/loading time, isn't it? I wonder that what is the perfect approach, fetching idea to use together `shouldChangeTextInRange` and `NSPredicate` – iamburak Jun 11 '16 at 08:39
  • @tobeiosdev This is a good, but separate question. Please post it as another question and let's see what options and ideas the community has. – Jay Jun 11 '16 at 11:54