Group Lab: Reorder TableView Rows

I’ve been working on some table view code with the aim of dragging rows to re-order them, like the World Clock list in iOS.

As I’ve hit a bit of a rock with this one, I thought it might be fun to see if anyone can take the code a bit further. I’ve plenty of ideas, but maybe someone else can crack it sooner…

To use this just create a new folder with the regular Widget library (copy the WidgetDemo from the sample code directory, if you need to) and drop the listing below in as the main file:

Matt
main.lua:
[lua]-- reorder

– status bar? what status bar?
display.setStatusBar(display.HiddenStatusBar)

– load the widget library
widget = require(“widget”)
widget.setTheme( “theme_ios” )

– ease of use values
local width, height, stage = display.contentWidth, display.contentHeight, display.getCurrentStage()

– forward references
local onRowRender, onRowTouch = nil, nil

– create the table view
local menulist = widget.newTableView{
left = 0,
top = 0,
width = width,
height = height,
maskFile = nil,
baseDir = nil
}
for k,v in pairs(menulist) do
if (type(v) == “function”) then
print(k)
end
end

– values for modifying the table view
local views = {} – list of rows from the menulist (should reflect the list of rows in the table view)
local enterFrameActive = false – true when the enterFrame event is running
local ySpeed = 0 – set when the user moves close to the top or bottom of the list (changes based on touch location)
local rowYspeed = 6 – the speed to reorder item rows (does not change)
local yOffset = 0 – the offset of the list when dragging starts (used to track the scroll position of the table view)

local defaultRowColor = { 174, 183, 190, 255 }
local defaultLineColor = {0,0,0,255}
local rowHeight = 45 – this is bad, but will have to do for now - assumes the height of each row is 44+1 for bordering (does not account for section headings and other heights yet)
local scrollAreaHeight = 20 – distance at top and bottom of tableView which will cause scrolling during a row drag
local adjustThreshold = 0 – distance a drag row gets from the centre of another row before the other row will readjust position

– just one big update (I tried many, many different methods and this was the most straight forward)
local function doUpdate()
– index of row being dragged
local dragIndex = menulist.dragTargetIndex

– update index of dragged row
local y = views[menulist.dragTargetIndex].view.y + rowHeight/2
local pos = math.round( (y+(rowHeight/2) + yOffset) / rowHeight )
if (pos < 1) then pos = 1 elseif (pos > #views) then pos = #views end
views[menulist.dragTargetIndex].pos = pos

– get start/end indices of affected rows
local a, b = dragIndex, pos
if (a > b) then b, a = dragIndex, pos end

– update pos value of rows before the affected area
for i=1, a-1 do
views[i].pos = i
end

– update rows being affected by the dragged row (the group moving up or down)
if (dragIndex < pos) then
for i=dragIndex+1, pos do
views[i].pos = i-1
end
elseif (dragIndex > pos) then
for i=pos, dragIndex-1 do
views[i].pos = i+1
end
end

– update pos value of rows after the affected area
for i=b+1, #views do
views[i].pos = i
end

– update positions on screen
for i=1, #views do
if (i ~= dragIndex) then
local y = (views[i].pos-1) * rowHeight + yOffset
if (views[i].view.y < y) then
views[i].view.y = views[i].view.y + rowYspeed
if (views[i].view.y > y) then views[i].view.y = y end
elseif (views[i].view.y > y) then
views[i].view.y = views[i].view.y - rowYspeed
if (views[i].view.y < y) then views[i].view.y = y end
end
end
end

– apply table scrolling (comment out for now due to unusual scrolling activity)
–[[
for i=1, #views do
if (i ~= dragIndex) then
if (yOffset < 0 and yOffset > -(#views*rowHeight)) then
views[i].view.y = views[i].view.y + yOffset
views[i].view.isVisible = true
end
end
end
]]–
end

– started by the drag tab to update row positions
local function enterFrame( event )
if (enterFrameActive) then
yOffset = yOffset + ySpeed
doUpdate()
return true
end
return false
end

– creates an enterFrame update for scrolling the whole table view
local function createEnterFrame()
if (not enterFrameActive) then
enterFrameActive = true
yOffset = menulist:getContentPosition()
Runtime:addEventListener( “enterFrame”, enterFrame )
end
end

– removes the enterFrame update to stop scrolling the whole table view
local function killEnterFrame()
if (enterFrameActive) then
Runtime:removeEventListener( “enterFrame”, enterFrame )
enterFrameActive = false
end
end

– INCOMPLETE: Needs to remove the dragged row and insert it into the drop position
local function dropRow()
local index = menulist.dragTargetIndex
local y = (views[index].pos-1) * rowHeight + yOffset
print(‘del’,index)
transition.to( views[index].view, { time=200, y=y } )
–[[menulist:deleteRow( menulist.dragTargetIndex )
menulist:insertRow( {
onEvent=onRowTouch,
onRender=onRowRender,
height=44,
isCategory=false,
rowColor=defaultRowColor,
lineColor=defaultLineColor,
issearch=true
}, views[menulist.dragTargetIndex].pos ) ]]–
end

– handles the dragging of a row based on the touch events of the drag tab
local function drag( event, rowEvent )
local row = rowEvent.target
local rowGroup = rowEvent.view
local index = rowEvent.index

if (event.phase == “began”) then
stage:setFocus( event.target )
menulist.dragTargetIndex = index
event.target.prev = event
stage:insert(rowEvent.view)
createEnterFrame()
elseif (event.phase == “moved”) then
local dist = event.y - event.target.prev.y
rowEvent.view.y = rowEvent.view.y + dist
event.target.prev = event

if (event.y < scrollAreaHeight) then
ySpeed = .1
elseif (event.y > height-scrollAreaHeight) then
ySpeed = -.1
else
ySpeed = 0
end
else
killEnterFrame()
dropRow()
stage:setFocus( nil )
event.target.prev = nil
menulist.dragTargetIndex = nil
end

return true
end

– typical touch function for a table view (not important in this program)
onRowTouch = function( event )
local row = event.target
local rowGroup = event.view
local index = event.index

if event.phase == “press” then
if not row.isCategory then rowGroup.alpha = 0.5; end
elseif event.phase == “release” then
if not row.isCategory then
– reRender property tells row to refresh if still onScreen when content moves
row.reRender = true
end
end

return true
end

– typical render function for a table view (adds the drag tab to each row)
onRowRender = function( event )
local row = event.target
local rowGroup = event.view
local index = event.index
print(‘onRowRender’,index)

views[#views+1] = event

local text = display.newRetinaText( 'item '…index, 12, 0, “Helvetica-Bold”, 18 )
text:setReferencePoint( display.CenterLeftReferencePoint )
text.y = row.height * 0.5

– create drag tab
local tab = display.newRect( rowGroup, width-50, 10, 25, 25 )
function d(e)
return drag( e, event )
end
tab:addEventListener( “touch”, d )

row.isDraggable = true

rowGroup:insert( text )
end

– create the list of data items for the table view
for i=1, 15 do
menulist:insertRow{
onEvent=onRowTouch,
onRender=onRowRender,
height=44,
isCategory=false,
rowColor=defaultRowColor,
lineColor=defaultLineColor,
issearch=true
}
end[/lua] [import]uid: 8271 topic_id: 30041 reply_id: 330041[/import]