Implementing a search function on all Products

search
swift

#1

I’m in the middle of developing an application in XCode with Swift using Moltin and a lot of other API’s. I’m looking to implement a search functionality in the app. that will allow users to search through the products and return a match, or else a message to say there was no similar products. I can’t find any code examples for this, has anyone tried this before and if so can they help me out? I’m using Xcode 9.1.
Thanks


#2

Hi Eoghan!

The filter parameter of MoltinQuery will let you be able to search products. This matches the API documentation at http://docs.moltin.com/?bash#product-filtering if you’d like any other filtering.

let query = MoltinQuery(
    offset: nil,
    limit: nil,
    sort: nil,
    filter: "eq(name, MyProduct)",
    include: [])

Thanks,
Craig


#3

Hi Craig,
Thanks for the reply.

What I would like to do is get a button on one ViewController, seperate to the Moltin VC’s, that will allow me to enter in a String and search through my Moltin products.
I am unsure how to use that query you have highlighted above, and then get my application to return a product if it finds one.

Is there an example somewhere of this functionality? Or if not could you let me know how I could put this functionality into a single function, and then call that function on the press of a button?


#4

Hey Eoghan,

I don’t believe we have this case in our example project at the moment, but it would be fairly straight forward.

Your view controller will need some input such as UITextField/UITextView/UISearchBar (linked as an @IBOutlet) and potentially a UIButton if you’re not using a UISearchBar. You can link the touch down of a UIButton by attaching an @IBAction to your view controller, or if you’re using a UISearchBar, link the delegate and implement searchBarSearchButtonClicked.

Once you’ve got the action wired up, you’d just want to grab the text from your UITextField/UITextView/UISearchBar, usually from a .text property, and pass that directly to the MoltinQuery, something like this:

let searchText = self.textField.text
let query = MoltinQuery(
    offset: nil,
    limit: nil,
    sort: nil,
    filter: "eq(name, \(searchText))",
    include: [])

Moltin.product.list(withQuery: query) { result in
   ...
}

Hope this helps,
Craig.


#5

Hi Craig,

Working on implementing this now and have another question.

I have a ResultsViewController, that I will enter this search parameter into your function. So I have a resultsTextField.text - and I want to enter that into my search function.

What I would like is for the system to recognise that there’s a matching Product, and then bring us to that ProductDetailViewController, or if there’s no matching product a simple message saying so.

I’m using your sampleV2 code to implement this so I have the ProductDetailViewController that you guys have made, is there a way to say in my Moltin.product.list query on my ResultsViewController ; “If there is a matching item navigate to that viewController” ?

If this question is hard to understand let me know and I’ll rephrase!

Thanks,
E


#6

All you’d need to do here is validate what the results of Moltin.product.list(withQuery:) comes up with, so your result block could look like so:

Moltin.product.list(withQuery: query) { result in
    switch result {
        case .success(let productList):
            if productList.products.count == 1 {
                // We have a single matching product, segue to the detail view
            }
   }
}

Hope this helps,
Craig


#7

In my if statement I have:
ProductDetailViewController.products = productList.products
ProductDetailViewController.collectionView?.reloadData()

But that’s giving me multiple errors, e.g. Type ‘ProductDetailViewController’ has no member ‘collectionView’

What would you suggest I do here?

Thanks,
E


#8

Looks like you’re attempting to use the ProductDetailViewController as a static class, rather than creating a new instance of it and pushing that onto the view. Depending on how you’ve got your project set up, with storyboards or directly with code, you’ll have to create new view controllers differently.

If you’re using Storyboards, take a look at prepareForSegue - more info here: https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html

If you’re creating everything in code, take a look a UINavigationController and pushViewController. Generally here you’d do something like:

let viewController = ProductDetailViewController()
viewController.products = productList.products
self.navigationController.pushViewController(viewController)

#9

I’m using the code from the sample project provided by yourselves.

Now that I’m using those 3 lines of code you gave, I’m getting the error: “Value of type ‘ProductDetailViewController’ has no member ‘products’” Although it does and I’ve made it not private.
If I change products to product, I then get the error: “Cannot assign value of type ‘[Product]’ to type ‘Product!’”

Sorry about the issues, I’m relatively new to Swift so fixing errors is proving to be v. difficult!


#10

My mistake, I’ve put there that I’m passing an array of products through, when the detail view controller that we have in our example code only accepts a single product.

Take a look at Line 81 in the list view controller on the example project, you’ll see how it’s setting up the detail - best to follow that:

This creates the controller, and gives it a single product, in your case if you have the productList it’ll look similar to this:

let controller = ProductDetailViewController()
controller.product = productList.products[0]

productList.products is an list of multiple products, so you’ll just want to grab a single product out of it.


#11

So this is my complete search method:

func searchTheResults(){
    let searchText = self.userInfo.text
    let query = MoltinQuery(
        offset: nil,
        limit: nil,
        sort: nil,
        filter: "eq(name, \(String(describing: searchText)))",
        include: [])

    Moltin.product.list(withQuery: query) { result in
        switch result {
        case .success(let productList):
            if productList.products.count == 1 {
                let controller = ProductDetailViewController()
                controller.product = productList.products[0]
            }
            case .failure(let error):
                // create the alert
                let alert = UIAlertController(title: "We're Sorry", message: "We found no matching results.", preferredStyle: UIAlertControllerStyle.alert)

                // add an action (button)
                alert.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.default, handler: nil))

                // show the alert
                self.present(alert, animated: true, completion: nil)
            }
    }
}

But when I press the button that calls this function nothing happens. My userInfo.text is a textfield I have in on my ViewController, I enter in the name of a product “Mens Shoes” and press the button that calls the searchTheResults method. But nothing happens - have I misunderstood something?


#12

Looks like you’re just missing the call to actually present that controller, right under:

controller.product = productList.products[0]

…you’ll want something like this (if you’re using a navigation controller):

self.navigationController.pushViewController(controller)


#13

I’m using a TabBarController as the main means of navigation around the app. But each ViewController also has a nav. controller so I assume that code should be ok?

I’m still receiving no response when I press the button that calls this searchTheResults method, I’ll try to de-bug it and see if I can find what’s causing the problem!


#14

Hi Craig,

I’m still not complete with this functionality just yet unfortunately.
Currently this is the method I’m calling which I hope will search my Moltin inventory, and if a matching product is found it will display that product to me.
func searchTheResults(){
let searchText = self.userInfo.text

    let query = MoltinQuery(
        offset: nil,
        limit: nil,
        sort: nil,
        filter: "eq(name, \(String(describing: searchText)))",
        include: [])

    Moltin.product.list(withQuery: query) { result in
        switch result {
        case .success(let productList):
            if productList.products.count == 1 {
                
                let controller = ProductDetailViewController()
                controller.product = productList.products[0]
                self.navigationController?.pushViewController(controller, animated: true)

            }
            case .failure(let error):
                // create the alert
                let alert = UIAlertController(title: "We're Sorry", message: "We found no matching results.", preferredStyle: UIAlertControllerStyle.alert)

                // add an action (button)
                alert.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.default, handler: nil))

                // show the alert
                self.present(alert, animated: true, completion: nil)
            }
    }
}

When I press the button, I get this JSON response in my Xcode console. But redirecting to another ViewController doesn’t seem to be working.

{
“access_token” = c2baa52fbcb2cac1c57b5d5f6cce63d9aedd961a;
expires = 1521541402;
“expires_in” = 3600;
identifier = implicit;
“token_type” = Bearer;
}

Can you see where I may be going wrong? I’ve played around with it for a few days and have made no progress. Thanks, E!


#15

Hey!

There’s nothing that immediately looks wrong to me there.

I’d suggest dropping some breakpoints or logging statements into your result block, that way you can find out whether or not the request succeeded, and how many products were returned. This should clear up exactly what’s happening, whether or not the result block was actually called, and if it’s even getting into the success case. I find breakpoints are the most helpful as you can step through your code line by line.


#16

The function seems to run all the way until the switch result under my Moltin.product.list query. Then it stops and doesn’t display either case! Are you sure a switch statement is the best for this overall query in this scenario?


#17

That’s defined by the Result enum we use, the has to be either a success or failure, as defined here

If it’s success, it’ll contain the result, if it’s an error, it’ll have the error object for you to inspect.

Since we only have two options and you’ve implemented both in the switch, it’ll have to go into one of those two cases. Drop a print statement below each case and check your console, that’ll let us know which it case drops in to.

If you’re finding that nothing is happening at all, it could be that it succeeds, but gets stopped by that if statement which is checking if only 1 product is returned.


#18

Edit:
It drops to the " if productList.products.count < 1 { " line and won’t proceed beyond that point. The last closure in my Network console is from the ResponseSerialization file in the Alamofire package!


#19

I’ve made some progress now - now it’s progressing through that function. But I’m getting an error in my ProductDetailViewController on line 36 - unexpectedly found nil while unwrapping an option value for my category.name.

Is there a way to bypass/hardwire this in? As for simplicity I only have one category in my inventory


#20

Nice one!

If you’re using the detail view controller from the example project, and you don’t need category, you could just remove references to it from the code.

There’s only 3 places to remove reference to it, L14 where it’s declared, L31-38 which is the category label setup, and L94 where the label is used.

Remove those 3 pieces and the category won’t be required in that view controller.