Lets work together to build a nice SearchBar sample app ...

This has to be one of the most common tasks for a business app and yet there is no Corona Labs sample showing how to implement one properly…

I have been looking around to see if any such thing exist but I could not find a recent (ie Widget 2.0) and simple sample so I decided to build one…Here’s a quick and dirty start. I used IOS Mail app search bar behavior as my model and even grabbed some of the graphics from there. Hope thats ok with Apple copyright etc. 

Attached is a modified main.lua for the ListView2 sample and some additional png files to go with it. I focused on the search bar creation, aesthetics and transitions etc. I did not implement the actual dataset reduction by search text entered as this will be different from app to app (ie SQLite vs other data set used etc). I think anyone making use of the search can/should put in their own dataset reduction routine…

The purpose of this sample is to do the Search bar creation, text entry, text entered being reset (little X in the Search bar) and ultimately the Search Cancel option. My idea is to put only what is pertaining to the search mechanics into this sample so anyone can take it and plug it into their own tableView based app with minimal tweaking…

It is a little crude at this point but I think its a good start. Please feel free to use as is or improve and post back on this thread so we can all make use of your improvements. 

Thank you very much in advance for any updates / improvements and corrections!!! Enjoy!

Edit : Attached main.txt is a copy of the main.lua. Simply rename back to .lua before use. Hope this makes the thread easier to use. 

Updated main.lua in first post with better transitions. Thanks to @jonjonsson for a great tip!

Site is claiming that the link for the main.lua.zip file:

http://forums.coronalabs.com/index.php?app=core&module=attach&section=attach&attach_id=1048

is an invalid URL…

fyi

Dewey

@dgaedcke - thanks for the heads up. I updated the file with an unzipped copy of the main.lua. This site does not permit uploads of lua files (funny…) so I renamed it to main.txt. Simply rename before use. Thanks

Updated version with

  1. better searchBar activation aesthetics
  2. checks implemented to only launch searchBar when its not launched. Row 1 was clickable even under the searchBar sometimes causing trouble. 
  3. some cleanup to commented code and some commenting for clarity… more to follow.

Use the attached main.txt and rename to main.lua. Enjoy!

Got some device testing. Not bad at all. The animations are not 100% exact but close enough to the IOS Mail app Search Bar.

I noticed that on the device the keyboard is not popping up initially when you tap Search so you need to tap again into the search field that appears… This can be fixed by putting the following in the searchBarActivate() function : 

        native.setKeyboardFocus( searchField )

Once you put this line in, the device behavior is closer to the native app experience where the keyboard pops up the second you tap the search bar at the top of your tableView.

Hope to get some feedback and even updates / suggestions. Don’t be shy now… :slight_smile:

This is really great work Ksan. Away from office at moment but really looking forward to digging into this when I get back on Monday

Hi Ksan,

Great to see community code, I will give it a try once I am back home.

Just wanted to ask - have you tried the widget.newSearchField?

Thanks
Atanas

Thanks guys. I look forward to your feedback. Its not much but its a start.

I did try the widget.newSearchField looking at the source to decipher its workings. I managed to get it to display and fire off the input field as well but the listeners didn’t seem to be complete. I think it will be great when CL eventually get this completed or released. Unfortunately I’m not proficient enough to try and improve it myself hence my simple ground-up approach.

The widget.newSearchField() isn’t in production yet.  I don’t know why and I’ve never tried to make it work, but in looking at the code, the “listener” that you pass to it is basically the same listener you would pass to native.newTextField().  We process the event first inside the widget, but then call  your function with the event table.  It appears that it’s your responsibility to handle the event for native.newTextField() and do with it what you want.

This could be as simple as waiting  until you get a “submitted” phase and then taking that string and looking things up in your data.  You could take the editing phase and do some time of search as you go feature but you would have to code up that functionality. 

Dunno if that nugget of info will help you try to work with our newSearchField() widget, but like I said, we didn’t make it live for a reason (and I don’t know what that reason is).

Rob

Thanks for the info Rob. Lets leave the newSearchField() widget out of this thread for now and keep this focused on a simple approach extending an existing CL sample app. This should be something anyone can pick and use once they have an understanding of the simple ListView2. In due course if an when CL updates and releases the newSearchField() widget then we can transition this sample to use it as well. 

Would love to get your feedback if you have a moment.

I just realized that creating the native.textField once and leaving it hidden when not in use might not be the best way to go forward. It is behaving a little funny in a storyBoard environment so perhaps removing it after use and recreating it again when needed might be a better approach. Will post another main.lua doing that later today.

Updated main.lua

Moved native.textField creation out of ORR into searchActivated function. 

Added code to remove native.textField once search is submitted or cancelled. 

When using storyboard, you should always create native objects in the enterScene() function and remove them in exitScene().   As far as leaving them around, if you want to do that, you would create it in main and make it some kind of global (i.e. add it to your mydata table since we don’t want real globals).  Then the difference scenes could reference it.

Rob

Got it. Thanks for the hints. The ListView2 sample does not use storyboard so my sample posted here does not either but in parallel I’m putting the searchBar into my storyboard based app so trying to keep as much reused as possible in best practices. So for now I just recreate native.textField when it’s needed and then dispose it when search is cancelled or submitted. Seems to work well.

Hi Ksan,

The event firs fine, however I have not tested the newSearchField on multiple devices yet. The code itself is pretty small and can be easily fixed if we encounter issues with it (the main issue being still the native fields staying on top, but thats something I am working on for a set of editFields as part of a widget library)

Here is the code that I use to create a search field in the toolbar:

    local function onSearchEvent(event)

      if event.phase == “submitted” then

          local search = event.target.text;

          if search and search:len() > 0 then

            local sFilter = event.target.text and ‘PLAYERS.firstName LIKE "’…search …

                                                   ‘" OR PLAYERS.lastName LIKE "’…search …’"’

                                                or ‘’;   

              

           myApp.dbPlayers:setFilter(sFilter)

         else

           myApp.dbPlayers:setFilter(nil);

         end  

         self:enterScene(nil)

       end  

    end;

        local searchField = widget.newSearchField

                              {

                              left = 0,

                              top = 0,

                              width = 200,

                              placeholder = “Search " …title…”…",

                              textFieldXOffset = 10,

                              listener = onSearchEvent,

                            }

        

        searchField.anchorX = 0.5;searchField.anchorY = 0.5; 

        searchField.x = display.contentCenterX

        searchField.y = titleBar.height * 0.5 + display.topStatusBarContentHeight

        parent:insert( searchField)

Hi atanas, thanks for sharing! I was getting stuck with the listener for some reason but I will give this another try. If we can get the official widget to work all the better of course. Thanks for your contributions by the way. Wow!!!

Yes, the native items should be in the enterScene(), but would also like to add that it (especially ‘textField’) be placed in a delay (i did it with delay + alpha transition). This is to prevent the textField from appearing suddenly on top of the screen (just a split second), and relocated to the correct x/y position. Some users may thin it is a glitch, but it is actually some re-drawing/rendering done.

Good tip. Thank you very much. The sample here addresses this issue in a transition.to call in the following manner : 

transition.to( searchField, { time=200, alpha=1 } )

searchField is initially created with an alpha=0.

 but would also like to add that it (especially ‘textField’) be placed in a delay (i did it with delay + alpha transition). This is to prevent the textField from appearing suddenly on top of the screen (just a split second)

Did you try this on Android device? I used to do this but gave that method up because it wouldn’t work on Android.

Quick update. I integrated Rob Miracle’s recent inputText tutorial into this SearchBox sample. Here’s the updated code. 

First the supporting widget_newTextField.lua

-- -- Include the existing widget library. When this is done, widget.newTextField will -- temporarily be added to the in-memory widget library. -- local widget = require( "widget" ) -- -- Forward declare the on screen textField Object -- local textField -- -- This is the starter code for a newTextField widget -- function widget.newTextField(options) local customOptions = options or {} local opt = {} -- -- Core parameters -- opt.left = customOptions.left or 0 opt.top = customOptions.top or 0 opt.x = customOptions.x or 0 opt.y = customOptions.y or 0 opt.width = customOptions.width or (display.contentWidth \* 0.75) opt.height = customOptions.height or 20 opt.id = customOptions.id opt.listener = customOptions.listener or nil opt.text = customOptions.text or "" opt.inputType = customOptions.inputType or "default" opt.keyboardType = customOptions.keyboardType or "done" opt.autoPopKeyboard = customOptions.autoPopKeyboard or false --opt.eraseInitText = customOptions.eraseInitText or false opt.font = customOptions.font or native.systemFont opt.fontSize = customOptions.fontSize or opt.height \* 0.67 -- Vector options opt.strokeWidth = customOptions.strokeWidth or 2 opt.cornerRadius = customOptions.cornerRadius or opt.height \* 0.33 or 10 opt.strokeColor = customOptions.strokeColor or {0, 0, 0} opt.backgroundColor = customOptions.backgroundColor or {1, 1, 1} -- -- Create the display portion of the widget and position it. -- local field = display.newGroup() local background = display.newRoundedRect( 0, 0, opt.width, opt.height, opt.cornerRadius ) background:setFillColor(unpack(opt.backgroundColor)) background.strokeWidth = opt.strokeWidth background.stroke = opt.strokeColor field:insert(background) if opt.x then field.x = opt.x elseif opt.left then field.x = opt.left + opt.width \* 0.5 end if opt.y then field.y = opt.y elseif opt.top then field.y = opt.top + opt.height \* 0.5 end -- create the native.newTextField to handle the input field.textField = native.newTextField(0, 0, opt.width - opt.cornerRadius, opt.height - opt.strokeWidth \* 2) field.textField.x = field.x field.textField.y = field.y field.textField.hasBackground = false field.textField.inputType = opt.inputType field.textField:setReturnKey(opt.keyboardType) field.textField.text = opt.text --field.textField.eraseInitText = opt.eraseInitText if opt.autoPopKeyboard then native.setKeyboardFocus( field.textField ) end if opt.listener and type(opt.listener) == "function" then field.textField:addEventListener("userInput", opt.listener) end -- -- Handle setting the text parameters for the native field. -- local deviceScale = (display.pixelWidth / display.contentWidth) \* 0.5 field.textField.font = native.newFont( opt.font ) field.textField.size = opt.fontSize \* deviceScale -- -- Sync the position of the native object and the display object. -- A 60 fps app will make this smoother than a 30 fps app -- -- You could add in things to handle other properties like alpha, .isVisible etc. -- that both objects support. -- local function syncFields(event) field.textField.x = field.x field.textField.y = field.y end Runtime:addEventListener( "enterFrame", syncFields ) -- -- Handle cleaning up the native object when the display object is destroyed. -- function field:finalize( event ) event.target.textField:removeSelf() end field:addEventListener( "finalize" ) return field end -- -- Do some fun things like move the text field -- Change the text. -- Remove it. -- --[[timer.performWithDelay( 5000, function() transition.to(textField, {time=1000, y=300, onComplete = function(target) target.textField.text = "Bye Bye"; end }) end )]]-- --timer.performWithDelay( 10000, function() textField:removeSelf(); end )