List interaction

This example demonstrates how to implement interactive list items in the Scripting app using swipe gestures. By leveraging leadingSwipeActions and trailingSwipeActions, you can provide contextual actions such as marking a message as unread, deleting a message, or flagging it.


Overview

You will learn how to:

  • Display a list of messages using a custom cell layout
  • Implement swipe actions on both leading and trailing edges
  • Configure swipe behavior (e.g. disabling full swipe)
  • Use Button, Label, and Circle for interactive UI elements

Example Code

1. Define Message Data Type

1type Message = {
2  from: string
3  content: string
4  isUnread: boolean
5}

2. Create a Custom Message Cell

Each message is rendered with a colored indicator (for unread status), sender name, and content using HStack and VStack.

1function MessageCell({
2  message
3}: {
4  message: Message
5}) {
6  return <HStack>
7    <Circle
8      fill={message.isUnread ? "systemBlue" : "clear"}
9      frame={{
10        width: 16,
11        height: 16,
12      }}
13    />
14    <VStack alignment={"leading"}>
15      <Text font={"headline"}>{message.from}</Text>
16      <Text>{message.content}</Text>
17    </VStack>
18  </HStack>
19}

3. Manage State and Actions

1const [messages, setMessages] = useState<Message[]>(...)
2
3function toggleUnread(message: Message) {
4  setMessages(messages.map(item =>
5    item !== message ? item : { ...message, isUnread: !item.isUnread }
6  ))
7}
8
9function deleteMessage(message: Message) {
10  setMessages(messages.filter(item => item !== message))
11}

4. Construct the List with Swipe Actions

1return <NavigationStack>
2  <List
3    navigationTitle={"Messages"}
4    navigationBarTitleDisplayMode={"inline"}
5    listStyle={"inset"}
6  >
7    {messages.map(message =>
8      <MessageCell
9        message={message}
10        leadingSwipeActions={{
11          allowsFullSwipe: false,
12          actions: [
13            <Button
14              action={() => toggleUnread(message)}
15              tint={"systemBlue"}
16            >
17              {message.isUnread
18                ? <Label title={"Read"} systemImage={"envelope.open"} />
19                : <Label title={"Unread"} systemImage={"envelope.badge"} />
20              }
21            </Button>
22          ]
23        }}
24        trailingSwipeActions={{
25          actions: [
26            <Button
27              role={"destructive"}
28              action={() => deleteMessage(message)}
29            >
30              <Label title={"Delete"} systemImage={"trash"} />
31            </Button>,
32            <Button
33              action={() => {}}
34              tint={"systemOrange"}
35            >
36              <Label title={"Flag"} systemImage={"flag"} />
37            </Button>
38          ]
39        }}
40      />
41    )}
42  </List>
43</NavigationStack>

5. Present the View and Exit

1async function run() {
2  await Navigation.present({
3    element: <Example />
4  })
5
6  Script.exit()
7}
8
9run()

Key Features

  • leadingSwipeActions: Add actions triggered by swiping from the leading edge (left-to-right in LTR layouts).
  • trailingSwipeActions: Add actions triggered by swiping from the trailing edge.
  • allowsFullSwipe: When set to false, prevents full swipe from automatically triggering the first action.
  • Button Roles: Use roles like "destructive" to style buttons (e.g., red for delete).
  • tint: Customize button color for better visual context.

Use Cases

  • Email/Messaging Scripts: Mark messages as read/unread, delete, archive, or flag.
  • To-Do Lists: Complete or remove tasks with quick gestures.
  • Custom Tools: Attach context-specific actions to list items.

Swipe actions provide an efficient and intuitive way for users to perform actions directly within list views, improving interaction speed and user experience.