Base data creation:

CREATE
  (l:List {id: 42, name: "Stuff"}),
  (i1:Item {id: "a", description: "Fry"}),
  (i2:Item {id: "b", description: "Bender"}),
  (i3:Item {id: "c", description: "Leela"}),
  (i1)-[:IN_LIST {position: 0}]->(l),
  (i2)-[:IN_LIST {position: 1}]->(l),
  (i3)-[:IN_LIST {position: 2}]->(l);

Cypher statement to move an item by its id property and 'swap' it with another item with id c. Here, 'swap' doesn't mean the literal meaning of swap, which would be to exchange the indices of the appropriate items. Moving an item to an item that is currently before it means that it gets moved before that item, while moving an item to an item after it means that it gets moved after that item. The net effect is a full range of movement possibilities.

MATCH
  (l:List {id: 42}),
  (i1:Item {id: "a"})-[r1:IN_LIST]->(l),
  (i2:Item {id: "c"})-[r2:IN_LIST]->(l)
WITH
    r1.position AS oldPosition,
    r2.position AS newPosition,
    r1 AS r1,
    CASE
      WHEN r2.position < r1.position THEN 1
      WHEN r2.position > r1.position THEN -1
      ELSE 0
    END AS signum
MATCH (i:Item)-[r3:IN_LIST]->(:List {name: "Stuff"})
WHERE
  (newPosition < oldPosition
 AND r3.position >= newPosition AND r3.position < oldPosition)
 OR (newPosition > oldPosition
AND r3.position > oldPosition AND r3.position <= newPosition)
SET r2.position = r2.position + signum, r1.position = newPosition;

Update December 2020: This is subtle enough that I thought that it merited its own repository with a test suite. See the implementation for more details on how this works. BTW I don't make any performance claims about this code.