Ok, I’ve had to do this on another thread (link follows) but here’s a gratuitous repost of the original thread…
Link: https://developer.coronalabs.com/forum/2012/05/29/regular-search-box
For this function you will need to take the existing Improved Widget Demo from the code exchange:
http://developer.anscamobile.com/code/improved-widgetdemo-sample
This improvement adds the iOS styled search box and search results to the (improved) Corona sample code WidgetDemo.
To use you need to add the following four images to the WidgetDemo directory and update the menustructure.lua and replace the menu.lua files.
To use in the app simply tap the search bar at the top, after pulling it down in the table view, and you’ll see the search bar. Type in the search bar and you’ll see items randomly generated from the PerformSearch function in menustructure.file. Hitting cancel will return to the previous menu.
You can add a search item row to any menu structure, but of course it looks better if you make it the first row and set the defaultscroll=2, to hide it, initially.
Your function to perform the search will receive three parameters:
parentitem: The menu item from the row which launched the search panel
term: The search term typed into the search box
callback: The function to call with your list of search items. This table is just like any other menu structure table.
The function used to search (below called ‘PerformSearch’) can make network connections and populate the results with the callback function parameter.
Please note: This is not fully tested on device or Apple simulator and the images will need some adjustment, but consider that a developer exercise.
Here’s how it should look:


The files to save into WidgetDemo:
defaultcancel.png: http://content.screencast.com/users/HoraceBury/folders/Default/media/185ebfca-beeb-49e1-83b1-fad26b80162e/defaultcancel.png
overcancel.png: http://content.screencast.com/users/HoraceBury/folders/Default/media/833b2acd-9276-4267-84a1-53f056013397/overcancel.png
searchcancel.png: http://content.screencast.com/users/HoraceBury/folders/Default/media/a0b768d4-f712-42fb-a691-d640f00fab91/searchcancel.png
searchwith.png: http://content.screencast.com/users/HoraceBury/folders/Default/media/c9039574-4ffd-418f-9220-b9eed5d70a54/searchwith.png
Replace the rootmenu table with the following additional function and updated rootmenu…
menustructure.lua:
[lua]function PerformSearch( parentitem, term, callback )
local list = {}
for i=1, math.random(1, 7) do
list[#list+1] = { id="", text=term…’ '…math.random(100,999), iscategory=false, dataitem=nil }
end
callback( list )
end
rootmenu = {
title=“Products”, defaultscroll=2, hasindex=true, hasscrollbar=true, canscrolltotop=true,
{ id="", text=“Search Bar”, issearch=true, dataitem=PerformSearch, },
{ id="", text=“Find”, iscategory=true, dataitem=nil, index=“F” },
{ id="", text=“By Product”, iscategory=false, dataitem=byproduct, candelete=true },
{ id="", text=“By Job”, iscategory=false, dataitem=byjobfunction, candelete=true },
{ id="", text=“By Region”, iscategory=false, dataitem=byregion, candelete=true },
{ id="", text=“Search”, iscategory=true, dataitem=nil, index=“S” },
{ id="", text=“Companies With…”, iscategory=false, dataitem=function(d,f) f(GetTestSubMenu()) end },
{ id="", text=“Info”, iscategory=true, dataitem=nil, index=“I” },
{ id="", text=“About…”, iscategory=false, dataitem=nil },
{ id="", text=“Magazines”, iscategory=true, dataitem=nil, index=“M” },
{ id="", text=“The Times”, iscategory=false, dataitem=nil },
{ id="", text=“The Independent”, iscategory=false, dataitem=nil },
{ id="", text=“The Sun”, iscategory=false, dataitem=nil },
{ id="", text=“News Feeds”, iscategory=true, dataitem=nil, index=“N” },
{ id="", text=“BBC News”, iscategory=false, dataitem=GetBbcNewsRss },
}[/lua]
Replace the whole menu.lua…
menu.lua:
[lua]-- menu
function newMenu( parent, rootdata, x, y, width, height )
– renders a title bar for a submenu
local function newTopBar( parent, title, scrollToBack, scrollToTop )
– local this bar
local group = display.newGroup()
group.widgets = {}
parent:insert( group )
– clean out the widgets
function group:clean()
for i=#group.widgets, 1, -1 do
group.widgets[i]:removeSelf()
group.widgets[i] = nil
end
group:removeSelf()
end
– status bar touch pad
if (scrollToTop) then
local touchbar = display.newRect( group, 0, 0, display.contentWidth, display.statusBarHeight )
function touchbar:touch( event )
if (event.phase == “began” and event.y <= display.statusBarHeight) then
scrollToTop()
end
return true
end
touchbar:addEventListener( “touch”, touchbar )
end
– create a gradient for the top-half of the toolbar
local toolbarGradient = graphics.newGradient( {168, 181, 198, 255 }, {139, 157, 180, 255}, “down” )
– create toolbar to go at the top of the screen
local titleBar = widget.newTabBar{
top = display.statusBarHeight,
topGradient = toolbarGradient,
bottomFill = { 117, 139, 168, 255 },
height = 44, width = 320
}
group:insert( titleBar.view )
group.widgets[#group.widgets+1] = titleBar
– create embossed text to go above toolbar
local titleText = display.newEmbossedText( title, 0, 0, native.systemFontBold, 20, { 255 } )
titleText:setReferencePoint( display.CenterReferencePoint )
titleText.x = display.contentWidth * 0.5 – (display.contentWidth * 0.5) – + x
titleText.y = 44 – + (titleBar.y + titleBar.height * 0.5)
group:insert( titleText )
group.widgets[#group.widgets+1] = titleText
– onRelease listener for back button
local function onBackRelease( event )
if (scrollToBack) then
scrollToBack( group.clean )
end
return true
end
– create ‘back’ button to be placed on toolbar
if (parent.parent.numChildren > 1) then
local backButton = widget.newButton{
label = “Back”,
left = 5, top = 28,
style = “backSmall”,
onRelease = onBackRelease
}
group:insert( backButton.view )
group.widgets[#group.widgets+1] = backButton
end
end – newTopBar
– renders a search input box for a search panel
local function newSearchBox( parent, cancel, scrollToTop, searchCallback )
– local this bar
local group = display.newGroup()
group.widgets = {}
parent:insert( group )
– declare the input text box
local inputbox = nil
– clean out the widgets
function group:clean()
inputbox:removeSelf()
for i=#group.widgets, 1, -1 do
group.widgets[i]:removeSelf()
group.widgets[i] = nil
end
group:removeSelf()
end
– status bar touch pad
if (scrollToTop) then
local touchbar = display.newRect( group, 0, 0, display.contentWidth, display.statusBarHeight )
function touchbar:touch( event )
if (event.phase == “began” and event.y <= display.statusBarHeight) then
scrollToTop()
end
return true
end
touchbar:addEventListener( “touch”, touchbar )
end
– search box image
local searchimg = display.newImage( group, “searchcancel.png” )
searchimg.xScale, searchimg.yScale = .5, .5
searchimg.x, searchimg.y = display.contentCenterX, searchimg.height/2-2
– onRelease listener for back button
local function onCancelRelease( event )
if (cancel) then
cancel( group.clean )
end
return true
end
– create ‘cancel’ button to be placed on toolbar
local cancelButton = widget.newButton{
label = “”,
left = 260, top = 27,
–style = “backSmall”,
default=“defaultcancel.png”,
over=“overcancel.png”,
width=110/2, height=58/2,
onRelease = onCancelRelease
}
group:insert( cancelButton.view )
group.widgets[#group.widgets+1] = cancelButton
– input box listener function
local function textListener( event )
– if (event.phase == “editing” or event.phase == “began” or event.phase == “ended”) then
if (event.phase == “editing”) then
–[[
local list = {
title=“Results”, defaultscroll=1, hasindex=false, hasscrollbar=false, canscrolltotop=true,
{ id="", text=event.text…"_One", iscategory=false, dataitem=nil, index=“F” },
{ id="", text=event.text…"_Two", iscategory=false, dataitem=nil, candelete=true },
{ id="", text=event.text…"_Three", iscategory=false, dataitem=nil, candelete=true },
{ id="", text=event.text…"_Four", iscategory=false, dataitem=nil, candelete=true },
{ id="", text=event.text…"_Five", iscategory=false, dataitem=nil, candelete=true },
}
]]–
searchCallback( event.text )
end
return true
end
– create native text input box
inputbox = native.newTextBox( 30, 30, 200, 25, textListener )
inputbox.isEditable = true
inputbox.align = “left”
inputbox.size = 16
end – newSearchBox
– creates the submenu or search panel
local function newSubMenu( contentdata, issearch )
– container for all submenu components
local submenugroup = display.newGroup()
parent:insert( submenugroup )
– timers etc
local posCheckTimer = nil
local adjustScrollBar = nil
– position the submenugroup
if (issearch == true) then
submenugroup.x, submenugroup.y = parent[parent.numChildren-1].x, parent[parent.numChildren-1].y
else
submenugroup.x, submenugroup.y = (parent.numChildren-1) * display.contentWidth, 0
end
– table view
local list = nil
– create tableView widget
list = widget.newTableView{
top = display.statusBarHeight + 44,
width = display.contentWidth,
height = 366,
maskFile = “assets/mask-320x366.png”
}
– keeps track of the number of rows
list.totalRowCount = 0
– keeps track of the total height of the widget content
list.totalheight = 0
– remove this if using later than build 2012.826
local function deleteAllRows()
while (list.totalRowCount > 0) do
list:deleteRow( 1 )
list.totalRowCount = list.totalRowCount - 1
end
end
– list of scroll index items
local function populateTableView( menudata, isnewdata )
local scroll = {}
– onEvent listener for the tableView
local function onRowTouch( event )
local row = event.target
local rowGroup = event.view
if (not row.isCategory) then
if (event.phase == “swipeLeft” and menudata[event.index].candelete == true) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=-(rowGroup.del.width/2+1),onComplete=rowGroup.del.setActive} )
elseif (event.phase == “swipeRight” and menudata[event.index].candelete == true) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=rowGroup.del.width/2+1,onComplete=rowGroup.del.setInactive} )
elseif (event.phase == “release” and not row.isCategory) then
row.reRender = true
print( “You touched row #” … event.index…" - ‘"…menudata[event.index].text…"’" )
– do population of submenu
local menuitem = menudata[event.index]
if (menuitem.dataitem ~= nil) then
– get the submenu’s data and add the new sub menu
– this will shift the parent group left to show a deeper submenu
local dataitem = menuitem.dataitem
– populate the submenu
function callback(dataitem)
– create the submenu or search item tableView
newSubMenu( dataitem, menuitem.issearch )
– use animation to show the next tableView
if (menuitem.issearch == true) then
– lay the search panel over the top of the current tableView menu (should be improved later to look like the smooth contacts list animation)
– ie: right now, do nothing (the search will just snap into place over the current tableView widget)
else
– slide the current tableView widget left to show the submenu animating into position
transition.to( parent, {time=300, x=parent.x-display.contentWidth} )
transition.to( topGroup, {time=300, x=parent.x-display.contentWidth} )
end
end
– if dataitem is a function, call it to get the data to put in the submenu, otherwise just populate the submenu
– if the dataitem is a function it must callback the passed in function to populate the submenu, this allows for network traffic delays
– the callback function does not need to be called, if the dataitem function wants to switch to another tab, for example
if (type(dataitem) == “function”) then
– if the item is a search, populate it with the initial search term
if (menuitem.issearch == true) then
– call the client function as an search
– callback( dataitem( {phase=“began”, term=""} ) )
callback( menuitem )
else
– call the client function with the parent item to get the submenu items
dataitem( menuitem, callback )
end
else
– populate directly because the submenu items are known here
callback( dataitem )
end
end
end
end
return true
end – onRowTouch
– onRender listener for the tableView
local function onRowRender( event )
local row = event.target
local rowGroup = event.view
local textFunction = display.newRetinaText
if row.isCategory then textFunction = display.newEmbossedText; end
if (event.index == 1 and menudata[event.index].issearch == true) then
– is a search bar
row.searchimg = display.newImage( rowGroup, “searchwith.png” )
row.searchimg.xScale, row.searchimg.yScale = .5, .5
row.height = row.searchimg.height/2
row.searchimg.x, row.searchimg.y = display.contentCenterX, row.searchimg.height/4
else
– is regular bar
row.title = textFunction( menudata[event.index].text, 12, 0, native.systemFontBold, 16 )
row.title:setReferencePoint( display.CenterLeftReferencePoint )
row.title.y = row.height * 0.5
if (not row.isCategory) then
row.title.x = 15
row.title:setTextColor( 0 )
local del = nil – the image sitting over the top of the button widget
local onButtonEvent = function (event )
print(‘del.isactive’,del.isactive)
if (not del.isactive) then
return false
end
if (event.phase == “release”) then
print( “You pressed and released a button!” )
end
return true
end
local myButton = widget.newButton{
id = “btn001”,
left = 225,
top = 9,
label = “”,
width = 90, height = 46,
cornerRadius = 8,
onEvent = onButtonEvent,
default=“deleteback.png”,
over=“deleteback.png”,
}
rowGroup:insert(myButton.view)
del = display.newImage(rowGroup,“deletebtn.png”)
del.x, del.y = 270, 32
del.xScale, del.yScale = .7, .7
del.isactive = false
rowGroup.del = del
local mask = graphics.newMask(“slidemask.png”)
del:setMask( mask )
del.maskScaleX, del.maskScaleY = -1, 1
del.maskX = del.width/2+1
del.maskY = 100
del.transtime = 300
function del:setActive()
print(‘active’,deletetap)
del.isactive = true
end
function del:setInactive()
print(‘inactive’)
del.isactive = false
end
end
– must insert everything into event.view:
rowGroup:insert( row.title )
end
end – onRowRender
– assign data to the rows
function createContent()
– Add rows
for i=1, #menudata do
local rowHeight, rowColor, lineColor, isCategory = 64
local menuitem = menudata[i]
if (menuitem.iscategory) then
isCategory = true; rowHeight = 24; rowColor={ 174, 183, 190, 255 }; lineColor={0,0,0,255}
end
– accumulate the total height of the content rows
list.totalheight = list.totalheight + rowHeight
list.totalRowCount = list.totalRowCount + 1
– insert the row into the tableView widget
list:insertRow{
onEvent=onRowTouch,
onRender=onRowRender,
height=rowHeight,
isCategory=isCategory,
rowColor=rowColor,
lineColor=lineColor,
issearch=true
}
– index can be a string or a function to return a string - string can be one char
if (menuitem.index ~= nil) then
local scrollitem = menuitem.index
if (type(scrollitem) == “function”) then
scrollitem = scrollitem( menuitem )
end
scroll[#scroll+1] = { index=i, char=scrollitem }
end
end
function addAlphaStrip(parent, callback)
local strip = display.newGroup()
parent:insert( strip )
strip.x, strip.y = display.contentWidth - 15, display.statusBarHeight+50
local alpha = display.newRoundedRect( strip, -10, 0, 20, display.contentHeight - 128, 10 )
alpha:setFillColor( 224, 224, 224 )
local size = 13.3
for i=65, 90 do
local index = i-64
local function touch(event)
for s=1, #scroll do
if (scroll[s].char == string.char(i)) then
list:scrollToIndex( scroll[s].index, 300 )
break
end
end
return true
end
local rect = display.newRect( strip, 0, 0, 40, size )
rect.x, rect.y = 0, 10+(i-65)*size
rect.alpha = 0
rect.isHitTestable = true
local letter = display.newText(strip, string.char(i), 0, 0, native.systemFont, 10)
letter:setTextColor(106, 115, 125)
letter.x, letter.y = 0.5, 10+(i-65)*size
rect:addEventListener( “touch”, touch )
end
end
if (isnewdata and menudata.hasindex) then addAlphaStrip( submenugroup ) end
– auto scroll to initial position
if (menudata.defaultscroll) then list:scrollToIndex( menudata.defaultscroll, 0 ) end
local currentpos = list:getScrollPosition()
list.maxscrolltop = 366-list.totalheight+24
–[[scroll bar handling]]–
local function addScrollBar()
local scrollbar = display.newRoundedRect( submenugroup, display.contentWidth - 5, display.statusBarHeight+50, 2.5, display.contentHeight - 128, 2.5 )
scrollbar:setFillColor( 255,0,0 )
scrollbar.lastchangetime = system.getTimer()
local adjustScrollBar = function( event )
if (list.totalheight < 366) then
scrollbar.alpha = 0
else
local starty = scrollbar.y
– check scroll position and adjust
local scrollpos = list:getScrollPosition()
if (scrollpos <= 0 and scrollpos >= list.maxscrolltop) then
local heightperc = (346 / list.totalheight) * 100
local scrollperc = (math.abs(scrollpos / math.abs(list.maxscrolltop))) * 100
scrollbar.height = 346/100*heightperc
scrollbar.y = 10 + display.statusBarHeight + 44 + (scrollbar.height/2)+((346-scrollbar.height)/100*scrollperc)
elseif (scrollpos > 0) then
local scrollperc = (scrollpos / 366) * 100
local heightperc = (346 / list.totalheight) * 100
heightperc = heightperc - scrollperc
scrollbar.height = 346/100*heightperc
scrollbar.y = 10 + display.statusBarHeight + 44 + (scrollbar.height/2)
elseif (scrollpos < list.maxscrolltop) then
local scrollperc = ((list.maxscrolltop - scrollpos) / 366) * 100
local heightperc = (346 / list.totalheight) * 100
heightperc = heightperc - scrollperc
scrollbar.height = 346/100*heightperc
scrollbar.y = 10 + display.statusBarHeight + 44 + (346 - (scrollbar.height/2))
end
– fade out the scrollbar
if (starty ~= scrollbar.y) then
scrollbar.lastchangetime = system.getTimer()
end
if (system.getTimer() - scrollbar.lastchangetime > 500) then
if (scrollbar.fadeintrans ~= nil) then
transition.cancel( scrollbar.fadeintrans )
scrollbar.fadeintrans = nil
end
if (scrollbar.fadeouttrans == nil) then
scrollbar.fadeouttrans = transition.to( scrollbar, { time=250, alpha=0 } )
end
else
if (scrollbar.fadeouttrans ~= nil) then
transition.cancel( scrollbar.fadeouttrans )
scrollbar.fadeouttrans = nil
end
if (scrollbar.fadeintrans == nil) then
scrollbar.fadeintrans = transition.to( scrollbar, { time=150, alpha=1 } )
end
end
end
end
Runtime:addEventListener( “enterFrame”, adjustScrollBar )
end – addScrollBar
if (isnewdata and menudata.hasscrollbar) then addScrollBar() end
end – populateTableView
– clear current tableView content
deleteAllRows()
list.totalRowCount = 0
list.totalheight = 0
– create the content
createContent()
end – populateTableView
– scroll to top function
if (contentdata.canscrolltotop) then
submenugroup.scrollToTop = function()
if (contentdata.defaultscroll) then
list:scrollToIndex( contentdata.defaultscroll, 400 )
else
list:scrollToIndex( 1, 400 )
end
end
end
– if this is a search panel provide the search bar at the top, if not provide a title bar
if (issearch == true) then
– is a search panel
– stops the search and removes the search panel
local function cancelSearch( onComplete )
print(‘cancelled’,parent.numChildren)
if (posCheckTimer) then
timer.cancel( posCheckTimer )
posCheckTimer = nil
end
Runtime:removeEventListener( “enterFrame”, adjustScrollBar )
onComplete()
list:removeSelf()
list = nil
submenugroup:removeSelf()
end
– gets the search results from the search callback and populates the tableview
local function searchCallback( searchterm )
– called by the menu’s search function to populate the tableView
function doPopulate( list )
populateTableView( list, false )
end
– call the menu’s search function to get the search results table
contentdata.dataitem( contentdata, tostring(searchterm), doPopulate )
end
– create search bar
newSearchBox( submenugroup, cancelSearch, submenugroup.scrollToTop, searchCallback )
else
– is a regular sub/menu
– scroll to origin - this is actually the back button effect
local function scrollToBack( onComplete )
function clean()
if (posCheckTimer) then
timer.cancel( posCheckTimer )
posCheckTimer = nil
end
Runtime:removeEventListener( “enterFrame”, adjustScrollBar )
onComplete()
list:removeSelf()
list = nil
submenugroup:removeSelf()
end
transition.to( parent, {time=300, x=parent.x+display.contentWidth, onComplete=clean} )
end
– create title bar
newTopBar( submenugroup, contentdata.title, scrollToBack, submenugroup.scrollToTop )
end – (issearch == true)
– insert widget into demoGroup
submenugroup:insert( list.view )
– populate the tableView with initial data
populateTableView( contentdata, true )
end – newSubMenu
newSubMenu( rootdata )
end[/lua]
[import]uid: 8271 topic_id: 29681 reply_id: 122593[/import]