Quick Start

Welcome to Scripting, an iOS app that lets you code UI components in TypeScript using React-like TSX syntax. With Scripting, you can create and present iOS utility UI pages through a familiar coding structure, using wrapped SwiftUI views for a smooth, native experience on iOS. This guide walks you through setting up your project, creating components, and working with hooks to build dynamic interfaces.

Table of Contents

  1. Getting Started
  2. Creating a Script Project
  3. Importing Components
  4. Creating Custom Components
  5. Presenting UI Views
  6. Using Hooks
  7. Building Complex UIs

1. Getting Started

In Scripting, you’ll create simple UI elements by defining them with function components. Every component and API you’ll need can be imported from the scripting package.

2. Creating a Script Project

Before you begin coding, you need to create a script project. Once the project is set up, write your code in the index.tsx file. This is your main entry point for defining UI components and logic.

Example setup in index.tsx:

1import { VStack, Text } from "scripting"
2
3// Define a custom view component
4function View() {
5  return (
6    <VStack>
7      <Text>Hello, Scripting!</Text>
8    </VStack>
9  )
10}

3. Importing Views

All views and some APIs from SwiftUI are wrapped and accessible through the scripting package. Here’s a list of some available views:

  • Layout Views: VStack, HStack, ZStack, Grid
  • Controls: Button, Picker, Toggle, Slider, ColorPicker
  • Collections: List, Section
  • Date and Time: DatePicker
  • Text and Labels: Text, Label, TextField

To use these in your project, import them as shown:

1import { VStack, Text, Button, Picker } from "scripting"

4. Creating Custom Components

Function components in Scripting work just like in React, with JSX-like syntax for building reusable components.

Example:

1import { VStack, HStack, Text, Button } from "scripting"
2
3function Greeting({
4   name
5}: {
6   name: string 
7}) {
8  return (
9    <HStack>
10      <Text>Hello, {name}!</Text>
11    </HStack>
12  )
13}
14
15function MainView() {
16  return (
17    <VStack>
18      <Greeting name="Scripting User" />
19      <Button 
20        title="Click Me" 
21        action={() => console.log("Button Clicked!")}
22      />
23    </VStack>
24  )
25}

5. Presenting UI Views

To present a UI view, use the Navigation.present method. This allows you to display a custom component as a modal view and handle its dismissal. The Navigation.present method returns a promise that fulfills when the view is dismissed. To avoid memory leaks, always call Script.exit() after the view is dismissed.

Example:

1import { VStack, Text, Navigation, Script } from "scripting"
2
3function View() {
4  return (
5    <VStack>
6      <Text>Hello, Scripting!</Text>
7    </VStack>
8  )
9}
10
11// Present the view
12Navigation.present({ 
13  element: <View />
14}).then(() => {
15  // Clean up to avoid memory leaks
16  Script.exit()
17})

In this example, Navigation.present({ element: <View /> }) displays the View component, and when the user dismisses it, Script.exit() ensures resources are freed.


6. Using Hooks

Scripting supports a range of React-like hooks for managing state, effects, memoization, and context. Here’s a guide on how to use each hook with examples:


useState

The useState hook lets you add local state to a function component.

1import { useState, VStack, Text, Button } from "scripting"
2
3function Counter() {
4  const [count, setCount] = useState(0)
5
6  return (
7    <VStack>
8      <Text>Count: {count}</Text>
9      <Button
10        title="Increment"
11        action={() => setCount(count + 1)}
12      />
13    </VStack>
14  )
15}

In this example, clicking the button updates the count variable, which automatically re-renders the component.


useEffect

The useEffect hook lets you perform side effects in your components, such as fetching data or setting up subscriptions.

1import { useState, useEffect, VStack, Text } from "scripting"
2
3function TimeDisplay() {
4  const [time, setTime] = useState(
5    new Date().toLocaleTimeString()
6  )
7
8  useEffect(() => {
9    let timerId: number
10
11    const startTimer = () => {
12      timerId = setTimeout(() => {
13        setTime(new Date().toLocaleTimeString())
14      }, 1000)
15    }
16
17    startTimer()
18    
19    return () => clearTimeout(timerId) // Clean up on unmount
20  }, [])
21
22  return <Text>Current Time: {time}</Text>
23}

In this example, the useEffect hook sets up an interval to update the time variable every second, and clears the interval on component unmount.


useReducer

The useReducer hook is useful for managing complex state logic in components.

1import { useReducer, VStack, Text, Button } from "scripting"
2
3type Action = { 
4  type: "increment"
5} | {
6  type: "decrement"
7}
8const reducer = (state: number, action: Action) => {
9  switch (action.type) {
10    case "increment":
11      return state + 1
12    case "decrement":
13      return state - 1
14    default:
15      return state
16  }
17}
18
19function Counter() {
20  const [count, dispatch] = useReducer(reducer, 0)
21
22  return (
23    <VStack>
24      <Text>Count: {count}</Text>
25      <Button 
26        title="Increment"
27        action={() => dispatch({ type: "increment" })}
28      />
29      <Button
30        title="Decrement"
31        action={() => dispatch({ type: "decrement" })}
32      />
33    </VStack>
34  )
35}

The useReducer hook helps you handle complex state transitions by using a reducer function.


useCallback

The useCallback hook lets you memoize functions, optimizing performance by preventing unnecessary re-creations of the function on every render.

1import { useState, useCallback, VStack, Text, Button } from "scripting"
2
3function Counter() {
4  const [count, setCount] = useState(0)
5
6  const increment = useCallback(() => {
7    setCount((prev) => prev + 1)
8  }, [])
9
10  return (
11    <VStack>
12      <Text>Count: {count}</Text>
13      <Button 
14        title="Increment"
15        action={increment}
16      />
17    </VStack>
18  )
19}

With useCallback, the increment function is only re-created when necessary, improving performance in large or frequently updated components.


useMemo

The useMemo hook lets you memoize values, caching expensive computations for better performance.

1import { useState, useMemo, VStack, Text, Button } from "scripting"
2
3function FactorialCounter() {
4  const [count, setCount] = useState(1)
5
6  const factorial = useMemo(() => {
7    let result = 1
8    for (let i = 1; i <= count; i++) result *= i
9    return result
10  }, [count])
11
12  return (
13    <VStack>
14      <Text>Factorial of {count} is {factorial}</Text>
15      <Button 
16        title="Increase"
17        action={() => setCount(count + 1)}
18      />
19    </VStack>
20  )
21}

The useMemo hook optimizes performance by only re-calculating the factorial when count changes.


useContext

The useContext hook allows components to access shared state across the app without prop drilling, using a Context API.

1import { createContext, useContext, VStack, Text, Button } from "scripting"
2
3const CountContext = createContext<number>()
4
5function Display() {
6  const count = useContext(CountContext)
7  return <Text>Shared Count: {count}</Text>
8}
9
10function App() {
11  return (
12    <CountContext.Provider value={42}>
13      <VStack>
14        <Display />
15      </VStack>
16    </CountContext.Provider>
17  )
18}

In this example, useContext accesses CountContext to get a shared count value across the app.


7. Building Complex UIs

Combine available views, hooks, and custom components to create complex, fully functional UIs.

Example:

1import { useState, VStack, Text, TextField, List, Section, NavigationStack, Script } from "scripting"
2
3function ToDoApp() {
4  const [tasks, setTasks] = useState(["Task 1", "Task 2", "Task 3"])
5  const [content, setContent] = useState("")
6
7  return (
8    <NavigationStack>
9        <List
10          navigationTitle="My Tasks"
11        >
12          <Section>
13            {tasks.map((task, index) => (
14              <Text key={index}>{task}</Text>
15            ))}
16          </Section>
17          
18          <TextField
19            title="New Task"
20            value={content}
21            onChanged={setContent}
22            onSubmit={() => {
23              if (content.length === 0) {
24                return
25              }
26              setTasks([...tasks, content])
27              setContent("")
28            }}
29          />
30        </List>
31    </NavigationStack>
32  )
33}
34
35async function run() {
36  await Navigation.present({
37    element: <ToDoApp />
38  })
39
40  Script.exit()
41}

For further details, check the full API documentation, which includes more examples and use cases for scripting package components and APIs.