[RESOLVED] Swipe to delete in Tableview

[Answer in following post]

Many applications give you the ability to “swipe to delete” or “swipe to edit”. I see that the TableView onRowTouch event provides these as phases and I’d like to have the swipeLeft phase reveal a little red Delete button and swipeRight to hide it again.

Has anyone implemented this and have some advice, please?

Thanks,
Matt. [import]uid: 8271 topic_id: 26616 reply_id: 326616[/import]

@Horace, firstly thank you for sharing!

By the way, isn`t it missing the final of the “onEvent listener for the tableView” function?
Thanks,
Rodrigo. [import]uid: 89165 topic_id: 26616 reply_id: 108518[/import]

Yes, I’ve not posted the whole file.I didn’t put any other code in. [import]uid: 8271 topic_id: 26616 reply_id: 108546[/import]

Where could I get the complete code?
Thanks, [import]uid: 89165 topic_id: 26616 reply_id: 108570[/import]

It’s just the widget demo in the sample code/interfaces directory, installed along with corona… [import]uid: 8271 topic_id: 26616 reply_id: 108593[/import]

Thank you @Horace!

.) [import]uid: 89165 topic_id: 26616 reply_id: 108594[/import]

Hi Horace.

I’ve been trying to get your code to work but I am not getting the same results.

Any chance I can see your full code to see what I am doing wrong?

Thank you!

B [import]uid: 64343 topic_id: 26616 reply_id: 108788[/import]

Sorry folks, but for some reason this is what I get when I run the original WidgetDemo code:

ERROR: bad argument #1 to display.newImageRect(): filename expected, but got nil.
Runtime error
?:0: attempt to index field ‘up’ (a nil value)
stack traceback:
[C]: ?
?: in function ‘?’
?: in function ‘?’
?: in function ‘new’
?: in function <?:2530>
(tail call): ?
/Users/eudoxus/Sites/Corona/WidgetDemo/main.lua:398: in main chunk
Runtime error: ?:0: attempt to index field ‘up’ (a nil value)
stack traceback:
[C]: ?
?: in function ‘?’
?: in function ‘?’
?: in function ‘new’
?: in function <?:2530>
(tail call): ?
/Users/eudoxus/Sites/Corona/WidgetDemo/main.lua:398: in main chunk

It works when I run my modified version, but that’s my app and I’ve broken the code into separate files. The WidgetDemo hasn’t changed but for some reason I get an error which doesn’t make any sense at all. [import]uid: 8271 topic_id: 26616 reply_id: 108891[/import]

So, I’ve tested this in a copied and modified version of the widget sample code, but not the original widget sample itself, so you might need to make some minor changes.

Swipe left on a row to reveal the delete button; Swipe right to hide it again; Delete button fires an event when pressed and released but otherwise drops the touch events through to the row beneath when hidden.

Widget button image:
http://content.screencast.com/users/HoraceBury/folders/Default/media/3f9572f4-030c-4654-9289-44849b0b1a27/deleteback.png

Delete button image:
http://content.screencast.com/users/HoraceBury/folders/Default/media/156bce20-452a-45bd-906a-21de75105c44/deletebtn.png

[New] Delete button mask:
http://content.screencast.com/users/HoraceBury/folders/Default/media/ef26dcb9-9b3f-498d-94ab-f5a4d06ec45a/slidemask.png

The onRowTouch function needs this:
[lua]-- onEvent listener for the tableView
local function onRowTouch( event )
local row = event.target
local rowGroup = event.view

if (event.phase == “swipeLeft”) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=-rowGroup.del.width/2,onComplete=rowGroup.del.setActive} )
elseif (event.phase == “swipeRight”) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=rowGroup.del.width/2,onComplete=rowGroup.del.setInactive} )
elseif (event.phase == “release” and not row.isCategory) then[/lua]

onRowRender function needs this:
[lua]-- 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

row.title = textFunction( “Row #” … event.index, 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
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[/lua] [import]uid: 8271 topic_id: 26616 reply_id: 108493[/import]

Thanks for the code… any idea why I am getting this error message on my Mac?
The file sandbox for this project is located at the following folder:
(/Users/byronfillmore/Library/Application Support/Corona Simulator/WidgetDemo-E572DCC64EED14454989F54E89BA81C0)
Runtime error
…s/CoronaSDK/SampleCode/Interface/WidgetDemo/main.lua:429: ERROR: table expected. If this is a function call, you might have used ‘.’ instead of ‘:’
stack traceback:
[C]: ?
[C]: in function ‘insert’
…s/CoronaSDK/SampleCode/Interface/WidgetDemo/main.lua:429: in main chunk
Runtime error: …s/CoronaSDK/SampleCode/Interface/WidgetDemo/main.lua:429: ERROR: table expected. If this is a function call, you might have used ‘.’ instead of ‘:’
stack traceback:
[C]: ?
[C]: in function ‘insert’
…s/CoronaSDK/SampleCode/Interface/WidgetDemo/main.lua:429: in main chunk
[import]uid: 64343 topic_id: 26616 reply_id: 109150[/import]

No, but I would make sure you’ve copied the code from the page accurately. Sometimes there’s characters which get inserted or changed. [import]uid: 8271 topic_id: 26616 reply_id: 109167[/import]

[EDIT: Cleaned up the onRowTouch event to remove my nasty debug code]

Ok, so i’m at my PC again and I can do some proper development with this. As I said, there are only two pieces of code two put in and three images to reference.

In my Windows simulator using build 704 this is the full code for the Sample Code/Interface/WidgetDemo/main.lua:

I have marked the two blocks below…

Unfortunately, I appear to have left out one of the images to be used:

http://content.screencast.com/users/HoraceBury/folders/Default/media/ef26dcb9-9b3f-498d-94ab-f5a4d06ec45a/slidemask.png

I shall update the original post with this as well.

The code below and these images should go into a (copy of) the WidgetDemo directory and it will give you left/right show/hide delete buttons in your table view rows.

[lua]–*********************************************************************************************

– ====================================================================
– Corona SDK “Widget” Sample Code
– ====================================================================

– File: main.lua

– Version 1.5

– Copyright © 2011 ANSCA Inc. All Rights Reserved.

– Permission is hereby granted, free of charge, to any person obtaining a copy of
– this software and associated documentation files (the “Software”), to deal in the
– Software without restriction, including without limitation the rights to use, copy,
– modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
– and to permit persons to whom the Software is furnished to do so, subject to the
– following conditions:

– The above copyright notice and this permission notice shall be included in all copies
– or substantial portions of the Software.

– THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
– INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
– PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
– FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
– OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
– DEALINGS IN THE SOFTWARE.

– Published changes made to this software and associated documentation and module files (the
– “Software”) may be used and distributed by ANSCA, Inc. without notification. Modifications
– made to this software and associated documentation and module files may or may not become
– part of an official software release. All modifications made to the software will be
– licensed under these same terms and conditions.

–*********************************************************************************************

display.setStatusBar( display.DefaultStatusBar )

local widget = require “widget”
widget.setTheme( “theme_ios” )

– create display groups
local demoGroup = display.newGroup()
local tabBarGroup = display.newGroup()

– forward declarations for widgets (so they can be removed manually)
local list, scrollBox, backButton, doneButton, pickerButton, picker

– private functions
local function clearGroup( g )
– remove all widgets (they must be removed manually)
if list then list:removeSelf(); list = nil; end
if scrollBox then scrollBox:removeSelf(); scrollBox = nil; end
if backButton then backButton:removeSelf(); backButton = nil; end
if doneButton then doneButton:removeSelf(); doneButon = nil; end
if pickerButton then pickerButton:removeSelf(); pickerButton = nil; end
if picker then picker:removeSelf(); picker = nil; end

– clear the contents of a group, but don’t delete the group
for i=g.numChildren,1,-1 do
display.remove( g[i] )
end
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
}

– position the demo group underneath the toolbar
demoGroup.y = display.statusBarHeight + titleBar.height + 1

– create embossed text to go above toolbar
local titleText = display.newEmbossedText( “Widget Demo”, 0, 0, native.systemFontBold, 20, { 255 } )
titleText:setReferencePoint( display.CenterReferencePoint )
titleText.x = display.contentWidth * 0.5
titleText.y = titleBar.y + titleBar.height * 0.5

– tab button listeners
local function showTableView( event )
clearGroup( demoGroup )

– create tableView widget
list = widget.newTableView{
width = 320,
height = 366,
maskFile = “assets/mask-320x366.png”
}

– insert widget into demoGroup
demoGroup:insert( list.view )

– onEvent listener for the tableView
local function onRowTouch( event )
local row = event.target
local rowGroup = event.view

if event.phase == “press” then

if not row.isCategory and row.title then
row.title.text = “Pressed…”
row.title:setReferencePoint( display.CenterLeftReferencePoint )
row.title.x = 15
end

elseif event.phase == “release” then

if not row.isCategory then
row.reRender = true
print( “You touched row #” … event.index )
end
end

–[[This part handles the swipe left and right to show and hide the delete button]]–
if (event.phase == “swipeLeft”) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=-rowGroup.del.width/2,onComplete=rowGroup.del.setActive} )
elseif (event.phase == “swipeRight”) then
transition.to( rowGroup.del, {time=rowGroup.del.transtime,maskX=rowGroup.del.width/2,onComplete=rowGroup.del.setInactive} )
end
–[[End of delete handling]]–

return true
end

– 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

row.title = textFunction( “Row #” … event.index, 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 )

–[[This part adds the swiped delete button and hides it with a mask…]]–
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
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 of new delete button]]–

end

– must insert everything into event.view:
rowGroup:insert( row.title )
end

– Add 100 rows, and two categories to the tableView:
for i=1,100 do
local rowHeight, rowColor, lineColor, isCategory

– make the 25th item a category
if i == 25 then
isCategory = true; rowHeight = 24; rowColor={ 174, 183, 190, 255 }; lineColor={0,0,0,255}
end

– make the 50th item a category as well
if i == 50 then
isCategory = true; rowHeight = 24; rowColor={ 174, 183, 190, 255 }; lineColor={0,0,0,255}
end

– insert the row into the tableView widget
list:insertRow{
onEvent=onRowTouch,
onRender=onRowRender,
height=rowHeight,
isCategory=isCategory,
rowColor=rowColor,
lineColor=lineColor
}
end
end

local function showScrollView( event )
clearGroup( demoGroup )

– create scrollView widget
scrollBox = widget.newScrollView{
–top = display.statusBarHeight + titleBar.height,
width = 320,
height = 366,
maskFile = “assets/mask-320x366.png”
}

– insert widget into demoGroup
demoGroup:insert( scrollBox.view )

local longImage = display.newImageRect( “assets/scrollimage.png”, 320, 1024 )
longImage:setReferencePoint( display.TopLeftReferencePoint )
longImage.x, longImage.y = 0, 0

– insert image into scrollView
scrollBox:insert( longImage )
end

local function showButtons( event )
clearGroup( demoGroup )

– set starting indexes for picker columns
local monthIndex, dayIndex, yearIndex = 10, 5, 21

– create grey background
local bg = display.newRect( 0, 0, display.contentWidth, display.contentHeight-demoGroup.y )
bg:setFillColor( 152, 152, 156, 255 )
demoGroup:insert( bg )

– create a label to show selected date
local dateText = display.newRetinaText( “No date selected.”, 0, 0, native.systemFontBold, 22 )
dateText:setTextColor( 0 )
dateText:setReferencePoint( display.CenterReferencePoint )
dateText.x = display.contentWidth * 0.5
dateText.y = 100
demoGroup:insert( dateText )

– onRelease listener for pickerButton
local function onButtonRelease( event )
– onRelease listener for back button
local function onBackRelease( event )

– remove the picker, the done button, and the back button (event.target)
if picker then picker:removeSelf(); picker = nil; end
if doneButton then doneButton:removeSelf(); doneButton = nil; end
if backButton then backButton:removeSelf(); backButton = nil; end

return true
end

– onRelease listener for the done button
local function onDoneRelease( event )

– extract pickerwheel column values
local dateColumns = picker:getValues()
local month, day, year = dateColumns[1].value, dateColumns[2].value, dateColumns[3].value

– mark column index so we can start in same position next time
monthIndex = dateColumns[1].index
dayIndex = dateColumns[2].index
yearIndex = dateColumns[3].index

– change the dateText’s label
dateText.text = month … " " … day … ", " … year

– remove the picker, the done button, and the back button (event.target)
if picker then picker:removeSelf(); picker = nil; end
if doneButton then doneButton:removeSelf(); doneButton = nil; end
if backButton then backButton:removeSelf(); backButton = nil; end

return true
end

– create ‘back’ button to be placed on toolbar
backButton = widget.newButton{
label = “Back”,
left = 5, top = 28,
style = “backSmall”,
onRelease = onBackRelease
}

– create ‘done’ button to be placed on toolbar
doneButton = widget.newButton{
label = “Done”,
left = 255, top = 28,
style = “blue2Small”,
onRelease = onDoneRelease
}

– set up the pickerWheel’s columns
local columnData = {}
columnData[1] = { “January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December” }
columnData[1].alignment = “right”
columnData[1].width = 150
columnData[1].startIndex = monthIndex

columnData[2] = {}
for i=1,31 do
columnData[2][i] = i
end
columnData[2].alignment = “center”
columnData[2].width = 60
columnData[2].startIndex = dayIndex

columnData[3] = {}
for i=1,25 do
columnData[3][i] = i+1990
end
columnData[3].startIndex = yearIndex

– create pickerWheel widget
picker = widget.newPickerWheel{
id=“pickerWheel”,
top=480, --258,
font=native.systemFontBold,
columns=columnData,
}

– slide the picker-wheel up
transition.to( picker, { time=250, y=258, transition=easing.outQuad } )
end

– create button to show pickerWheel
pickerButton = widget.newButton{
label = “Show Picker”,
left = 22, top = 285,
onRelease = onButtonRelease
}

– insert picker button into demoGroup
demoGroup:insert( pickerButton.view )
end

– create buttons table for the tab bar
local tabButtons = {
{
label=“tableView”,
up=“assets/tabIcon.png”,
down=“assets/tabIcon-down.png”,
width=32, height=32,
onPress=showTableView,
selected=true
},
{
label=“scrollView”,
up=“assets/tabIcon.png”,
down=“assets/tabIcon-down.png”,
width=32, height=32,
onPress=showScrollView,
},
{
label=“button”,
up=“assets/tabIcon.png”,
down=“assets/tabIcon-down.png”,
width=32, height=32,
onPress=showButtons,
}
}

– create a tab-bar and place it at the bottom of the screen
local demoTabs = widget.newTabBar{
top=display.contentHeight-50,
buttons=tabButtons
}

– insert tab bar into display group
tabBarGroup:insert( demoTabs.view )

– start off in the tableView tab
showTableView()[/lua]
[import]uid: 8271 topic_id: 26616 reply_id: 109020[/import]

byron5 – I know it’s a few months gone, but for anyone who runs into that problem it boils down to 3-4 places in the code, such as this:

tabBarGroup:insert( demoTabs.view )

That line right toward the end of the code needs to say:

tabBarGroup:insert( demoTabs )

The .view thing was dumped in later versions of the SDK. Do a search for .view and make those changes and the code should run. Make sure you don’t change things like event.view, those are okay. You’re only wanting to change the places where a widget is getting inserted into a display group.

Jay
[import]uid: 9440 topic_id: 26616 reply_id: 121120[/import]

byron5 – I know it’s a few months gone, but for anyone who runs into that problem it boils down to 3-4 places in the code, such as this:

tabBarGroup:insert( demoTabs.view )

That line right toward the end of the code needs to say:

tabBarGroup:insert( demoTabs )

The .view thing was dumped in later versions of the SDK. Do a search for .view and make those changes and the code should run. Make sure you don’t change things like event.view, those are okay. You’re only wanting to change the places where a widget is getting inserted into a display group.

Jay
[import]uid: 9440 topic_id: 26616 reply_id: 121120[/import]

I actually implemented this in Speed Sender, my templated SMS sender app. But I had to engineer a solution and I’m sure it would be considered hackish. There were so many edge cases it wasn’t even funny.

I had an edit button which showed the red minus button, then tap on the row to turn that button and show a delete button.

I had to add swipe to delete, but touching other rows had to go undo all of it. It was a pain in the backside.

[import]uid: 19626 topic_id: 26616 reply_id: 123210[/import]

I actually implemented this in Speed Sender, my templated SMS sender app. But I had to engineer a solution and I’m sure it would be considered hackish. There were so many edge cases it wasn’t even funny.

I had an edit button which showed the red minus button, then tap on the row to turn that button and show a delete button.

I had to add swipe to delete, but touching other rows had to go undo all of it. It was a pain in the backside.

[import]uid: 19626 topic_id: 26616 reply_id: 123210[/import]