Welcome!

You're reading the Zing Guide. The Guide walks you through everything you need to know to become a Zing master.

The Zing Reference covers all the different Zing elements and APIs. You access the Reference by switching tabs at the top of this window.

Help shortcut

⌘FFind a section, element or API

If you can't find the answers you're looking for, please get in touch!

Getting started

Create a new project using the "File » New Project..." menu.

A Zing project is a folder containing your code and asset files. Projects are self-contained, and can be moved or shared by copying the project folder.

Previewing

In Zing running your prototype is called "previewing".

Previewing is automatic. As you modify the project in Zing Studio, the preview will update in real time. You can preview a project on your Mac, with the builtin simulator, or on an iOS or Android device, using the Zing Preview app.

The Preview Selector, in the top right of the Zing Studio window, is used to switch between the available preview targets, or to temporarily disable previewing.

Simulator

The simulator can emulate a range of iOS and Android devices.

You configure the simulator via the "Preview » Simulator" menu. From there you can choose the device to simulate and adjust its orientation and scale.

Simulator shortcuts

⌘+Zoom in
⌘-Zoom out
⌘←Rotate left
⌘→Rotate right

Zing Preview

The free Zing Preview app allows you to preview directly on iPhone, iPad and Android devices. The simulator and Zing Preview both provide the same functionality, but Zing Preview is way more fun!

When Zing Preview is open, your phone should appear automatically in the Preview Selector list. Zing Studio and Zing Preview communicate over WiFi, so if it isn't showing check that your Mac and device are connected to the same WiFi network.

If you're using an iOS device, you can also connect via a lightning cable when WiFi isn't available. When your device is plugged in, it will appear with a small cable icon next to it in the Preview Selector.

Basic concepts

This section discusses the "Scaling Box" project.Save

Whenever the guide refers to a sample project, a banner like the one above will appear. Click "Save" to create a copy of the project.

Similarly, some examples in the guide have a "Show Me" button which will create a project to demonstrate the concept in a more complete context.

Once you're done, it is easy to keep or delete the samples.

Elements

Prototypes are built by combining components called elements.

Zing comes with a number of builtin elements and you can also build your own to use in your project. The complete list of builtin elements can be found in the reference documentation.

An element is a reusable component. When you use an element you are creating an instance of that element (often called an "element instance", or simply an "object"). For example, while there is only one Rectangle element type, you can have many Rectangle objects.

Visual elements, like those shown above, are called items. Prototypes start at a single root item, whose children and children's children etc form the visual tree which appears on screen.

Design View

The designer works just like a regular design tool, allowing you to visually position and configure the items in your project.

The outline panel on the right side of the designer shows the tree of elements that make up the design. The outline of the Scaling Box project is shown below. The root of the project is a Rectangle with four children, an Apple.NavigationBar, an Apple.Slider, another Rectangle and an Apple.Button.

There is no need for a special group or layer item in Zing, as every item can contain children.

New items are added by dragging them from the Element Library directly onto the canvas or into their desired position in the outline panel. You can reorder items and change their container by dragging them around in the outline.

When an item is selected, you can change its properties in the properties panel. You can use the escape key to quickly jump back to the outline panel when the properties panel is open.

Try experimenting with the designer by changing the center rectangle's color, or by repositioning the slider.

Code View

You switch between the design and code views using the tabs above the editor. The Scaling Box project code looks like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Rectangle as root {
    color: #white
    
    Apple.NavigationBar {
        anchor boundsX
        title: "Scaling Box"
    }
    
    Apple.Slider as slider {
        anchor boundsX = parent.boundsX - 8
        anchor top = previous.bottom + 8
        maxValue: 2.5
        minValue: 0.5
        value: 1.5
    }
    
    Rectangle {
        anchor center
        scale: 1
        color: #lightgreen
        height: 100
        width: 100
    }

    Apple.Button {
        anchor centerX
        anchor bottom = parent.bottom - 8
        label: "Reset"
    }
}

The first thing you will notice is how the structure of the design and the code are remarkably similar. Both are simple trees of elements and property values.

With Zing, the design and the code are not two separate concepts. Instead they are just different ways of viewing the same content. Changes you make in the designer are reflected in the code, and changes you make in the code appear in the designer.

Try changing the rectangle color from #lightgreen in the code view, and then flip back to the designer to see its effect.

Coding

Each Zing file defines a tree of elements. Elements are written in code using the following form:

ElementType as identifier {

    initializers

    child elements

}

The identifier is an optional name that can be used by code to reference that specific object. As it identifies a specific object, each identifier name must be unique. As elements are not required to have an identifier, it can be omitted when not needed.

The identifier name can be any length, but it always starts with a lower case letter, and can only contain letter, number and _ characters. For example, 'mySlider7' and 'the_navigation_bar' are both valid identifiers. The Scaling Box example uses two object identifers - 'root' and 'slider'.

Inside the element declaration, different types of initializers are used to configure element. Initializers consist of things like anchor declarations, property assignments and event handlers.

Anchor declarations

Anchors are used to position items relative to their container or siblings. Anchors are discussed in detail in the Guide: Anchors chapter.

Property assignments

Property assignments set different element values. The basic property syntax is as follows:

propertyname : value

Here are some examples of assigning different types of properties:

Rectangle {
    color: #lightblue
    radius: 8
}
Image {
    source: "uluru.jpg"
}
Text {
    size: 24
    text: "Hello, world!"
}
Apple.SearchBar {
    style: .Minimal
}

The examples above assign properties exact values such as the number 8, or the color #lightblue. These are called "literal" values. Properties can also be assigned expressions, and Zing will automatically calculate the correct value.

For example, in the Scaling Box project, change the light green rectangle's scale property from the literal value "1" to the expression "slider.value", so that it looks like this.

17
18
19
20
21
22
23
Rectangle {
    anchor center
    scale: slider.value
    color: #lightgreen
    height: 100
    width: 100
}

You should immediately notice in the preview that the rectangle is larger. As the slider's initial value is 1.5, the rectangle is now also scaled by that amount.

Property expressions are reactive. As you move the slider the rectangle's scale is automatically updated to reflect the slider's current value. Try it out in your project, or in the example embedded below.

Event handlers

Elements emit events when something happens, such as a button being tapped. Event handlers allow you to write code to respond to the event.

For example, adding the following code to the Scaling Box project will restore the slider back to its initial value of 1.5 when the "Reset" button is activated.

25
26
27
28
29
30
31
32
33
Apple.Button {
    anchor centerX
    anchor bottom = parent.bottom - 8
    label: "Reset"

    on activated {
        slider.value = 1.5
    }
}

Children

Children appear after the property assignments and event handlers. There is no need for a separate group or layer item in Zing, as every item can have children.

Child items are positioned relative to their container. You can learn more about positioning children in the Guide: Layout section.

Inspecting

Once you start using property expressions or interacting with your prototype, it isn't always obvious what the value of an element's properties are. Zing allows you to quickly inspect the current value of any property in a running prototype.

As the inspection mechanisms all show the runtime values of a prototype, they can only be used when your project is actively being previewed. If you have disabled preview, or your project contains errors, none of the inspection tools will be available.

Quick look

If you hover your mouse over an Item element in the code outline panel, or in the designer, it will be automatically highlighted on screen in the project preview. The same effect can be achieved in the code view by positioning the cursor within the element name and hovering over the highlighting eye icon that appears.

Quick look lets you get a simple view of where an item is being positioned on screen.

Microscope

The concrete value of a property expression can change over time depending on the expression's inputs. The Zing "microscope" allows you to peek at the current value of a property expression.

You can enable the microscope by tapping the microscope icon below the code outline, or by using the microscope shortcut key, when the project is being previewed.

Microscope shortcut

⌘⇧MToggle microscope on or off

The image below shows the initial state of the slider and rectangle elements in the Scaling Box prototype. The microscope has inserted the concrete value of the rectangle's scale property inline with the rest of the code.

As you interact with the slider, the microscope updates in real time. This is what it looks like if you drag the slider all the way to the right at its maximum value.

Inspect panel

When you want to see the value of all of an element's properties, you can use the inspect panel.

To view a specific object, select it in the design view or position the cursor inside it in the code view. As with the microscope, the property values shown in the inspect panel will update in real time as you interact with the running prototype.

Layout

Coordinate system

Zing uses a left-hand coordinate system, with the origin at the top-left corner of the screen. The root item in the prototype is sized to fill the entire screen.

Your prototype is layered under the system status bar. If present, the Android navigation bar may be docked on the bottom or right side of the device, and reduces the effective screen space available to your prototype.

Sizes are always specified in point units rather than pixels, and Zing dynamically adjusts the point size depending on the display's pixel density.

As shown above, the screen space available to your prototype on the iPhone 6 is 375x667pt and on the Nexus 5X it is 411x683pt.

Android Devices

Android devices use a surprisingly wide variety of screen scaling factors, and traditional Android design is often still done in pixels as a result.

Zing uses the scaling factor reported by the device to map between points and pixels. For example, the Nexus 5X uses a scaling factor of 2.625x, which gives its 1080p screen a point resolution of 411x711 (48pt of which is taken up by the navigation bar).

Positioning

The most basic way to set an item's position and size is to manually specify the x, y, width and height properties using property expressions.

By default, items are drawn in the order they are written. So children are drawn on top of their container, and later siblings will overlap earlier ones.

Rectangle {
    color: #lime
    Rectangle {
        width: 100; height: 100
        color: #orange
    }

    Rectangle {
        x: 50; y: 50
        width: 100; height: 100
        color: #lightblue
    }
}

Items are positioned relative to their container. Here we add a small blue rectangle as a child of the orange rectangle. Even though the slider below the canvas only changes the outer rectangle's rotation, the child is rotated as well.

Rectangle {
    width: 100; height: 100
    rotation: 12
    color: #orange
    radius: 2
    
    Rectangle {
        x: 40; y: 40
        width: 50; height: 50
        color: #lightblue
    }
}

As with all properties, you can also assign expressions to the x, y, width and height properties. While the size slider varies the orange rectangle's size, the blue rectangle's x and y expressions keep it fixed to the horizontal center of its container's bottom edge.

Rectangle {
    width: 100; height: 100
    color: #orange
    radius: 2
    
    Rectangle {
        x: (parent.width - width) / 2
        y: parent.height - height
        width: 50; height: 50
        color: #lightblue
    }
}

Anchors

Using property expressions for all positioning can quickly become cumbersome. Anchors provide an alternative to explicitly positioning items, by allowing you to declaring a relationships between one item and another.

You can achieve the previous example using anchors like this:

Rectangle {
    width: 100; height: 100
    color: #orange
    radius: 2
    
    Rectangle {
        anchor centerX
        anchor bottom
        width: 50; height: 50
        color: #lightblue
    }
}

Anchors and explicit values can be mixed. We used the width and height properties to specify the size, but used anchors to set the position. Just like with property expressions, the position is correctly updated as the the orange rectangle's size changes.

Anchors can be chained together to create more complex layouts.

Rectangle as icon {
    anchor left = 10
    width: 30; height: 30
    color: #lightblue
}

Text {
    anchor left = icon.right + 8
    anchor right = more.left - 8
    text: "Samantha Louise Applebee"
}

Ellipse as more {
    anchor right = parent.right - 10
    width: 20; height: 20
    color: #lightblue
}

This is the general form of an anchor.

anchor Attribute = Coefficient * Source Attribute + Constant

The available attributes are left, centerX, right, boundsX, top, centerY, bottom, boundsY, width, height, center, bounds and size.

For each attribute, there is also a equivalent for the same value in the item's safe area. They are named safeAreaLeft, safeAreaCenterX, safeAreaRight, safeAreaBoundsX, safeAreaTop, safeAreaCenterY, safeAreaBottom, safeAreaBoundsY, safeAreaWidth, safeAreaHeight, safeAreaCenter, safeAreaBounds and safeAreaSize.

Layout containers

Layout containers are items that automatically position their children. Layout containers are useful when you have a number of regularly arranged items, such as icons in a row or column.

Column {
    spacing: 10
    Rectangle {
        width: 60; height: 80
        color: #red
    }
    Rectangle {
        width: 60; height: 70
        color: #orange
    }
    Rectangle {
        width: 60; height: 60
        color: #green
    }
}

Zing includes four layout containers.

ScrollView

The ScrollView element allows large content to be scrolled. When the dimensions of the ScrollView's children exceeds its own size, the ScrollView enables scrolling in the appropriate direction.

ScrollView {
    width: 320
    height: 480

    InfoPane {
        width: 320
        landmark: "Royal Albert Hall"
        city: "London, England"
    }
}

As the InfoPane child element is taller than the ScrollView, it is allowed to scroll vertically. The ScrollView can also have multiple children, in which case the bounds of all the children are used to determine the scroll dimensions.

User elements

Elements are the foundational building blocks of Zing. They are the components of the Zing world. In the examples so far you have assembled the builtin elements to create simple prototypes.

You can also build your own elements. Elements you build yourself are used in the same way as the builtin elements. Anywhere you can use a builtin element you could also use a custom element you have made.

New elements are made just like the rest of your prototype - by combining existing elements and logic code to create something new. This is simple, but also extremely powerful. For example, the Apple.Slider control shown below is actually built out of two Rectangles and an Image. Double click on it to see the constituent elements.

Elements are extremely versatile. You can build elements for user interface controls you require, such as the slider example shown above. You can also build elements for smaller fragments of your UI. For example, if you are building a music app, you might create an element for a track cell.

Elements are also great for splitting your project into more managable pieces, allowing you to work on parts of it in isolation. In most cases each fullscreen view in your prototype should be its own element.

Elements don't need to be big and complex. It is perfectly fine, although perhaps a little extreme, to create a RedText element that is made of a single Text element like this.

From RedText
Text {
    color: #red
    font: "San Francisco"
    size: 22
}

When you use the RedText element in your project, it behaves exactly like regular Text, except it is red (and larger) by default.

Image carousel

This section discusses the "Image Carousel" project.Save

Image Carousel is a concrete example prototype that demonstrates how you can use custom elements. It looks like this.

The project is made up of three user elements - App, Button and ImagePage. The project's custom elements are shown in the project explorer. You can create a new element by clicking the plus icon and entering its name.

In the same way as you drag builtin elements out of the Element Library and drop them on the canvas, you can drag users elements out of the project explorer. Try adding a Reset button between the Previous and Next buttons that scrolls back to the first carousel page.

The Button element is used for the previous and next buttons in the bottom corners of the prototype.

The ImagePage element displays an image from the network within a frame and shows a busy indicator while it is loading. A standalone ImagePage is shown below.

Finally, the App element provides the prototypes' main view and logic, by combining two buttons and three image page's to create the completed example.

Home element

The App element is the main view of the Image Carousel example. The small home symbol next to it in the project explorer indicates that it is the "home element".

Even though it has an important sounding name, there is nothing special about the App element. From Zing's perspective it is just another element. If you rename the App element, it continues to work the same.

The home element is the element that Zing shows when it previews your prototype. This is a subtle, but important, detail. Zing doesn't "run" your prototype - it just creates an instance of the home element and displays it on the screen. Zing really is elements all the way down.

You can make another element the home element by activating the home icon that appears when you mouse over it. Try switching between the three elements in the Image Carousel project to see how the preview changes.

Element interface

The root object in your custom element is called the element's "base" type. The custom element is said to extend the base element type.

The skeleton code for an example Button element is shown below. In this case, the Button extends the Rectangle element.

From Button
Rectangle {
    Text as label {
        text: "Button"
    }
}

When you build an element you can set any internal property that you need, like the label's text. However, when you use the element you can only access the properties that are on the root object of the element. For example, this is OK,

Button {
    color: #blue
}

but this is not:

Button {
    text: "Press Me" // Error: Unknown property
}

If a property isn't on the root of your element, it can't be accessed outside the element itself. There is no way to "reach into" an element from the outside and change an arbitrary property. Only the details of your element that you choose to expose are available from the outside.

An element's properties, events and functions are collectively called the element's "interface". In order to build powerful new components you can add new properties, events and functions to your elements.

Properties

You declare new properties like this:

Item {
    property Int count
    property String name
}

You can add new properties to any object in an element. The properties declared on the root object of an element become part of the its interface. Properties on other objects can store values used in your code.

Properties can also be marked as private or readonly.

Item {
    private property Int internalProperty
    readonly property String readonlyProperty
}

A private property is only visible within the file it is declared in. To code outside that file it is as though the property doesn't exist.

A readonly property can only be modified by code within the file it is declared in. To code outside that file, it's value can be read but not modified.

Events

You add events to your own elements like this:

Item {
    event pressed()
    event pressedAtLocation(Float x, Float y)
}

If the event is declared on the root object, it becomes part of the element's interface.

Events can be marked as private.

Item {
    private event internalEvent()
}

A private event is only visible within the file it is declared in. To code outside that file it is as though the event doesn't exist.

When an event includes arguments, such as the x and y position shown above, you can access them by adding argument receivers to your event handler:

on pressedAtLocation(xPosition, yPosition) {
    System.log("Pressed at", xPosition, yPosition)
}

The names you choose for the event arguments, in this case x and y, are used for code suggestion to communicate what the receiver is for, but can be overridden by event handlers.

To fire an event, you call it like a function.

root.pressedAtLocation(10, 11.1)

Functions

You can add functions to your own elements like this:

Item {
    // "myFunction" takes no arguments
    function myFunction() {
        System.log("myFunction called")
    }

    // "myFunction1" takes a single int argument
    function myFunction1(Int arg1) {
        System.log("myFunction1 called with ", arg1)
    }

    // "myFunction2" takes a String and a Rectangle 
    // and returns a Bool
    function myFunction2(String arg1, Rectangle item) -> Bool {
        return true
    }
}

Functions can be marked as private.

Item {
    private function internalFunction() {
        System.log("internalFunction called")
    }
}

A private function is only visible within the file it is declared in. To code outside that file it is as though the function doesn't exist.

Aliases

An alias creates an alternative name for an existing property, event, function or object for use in an element's interface.

Often when building a element, you need to expose a property of an internal object. Creating an alias does this - it creates a new property on the root object that behaves exactly like the alias target. Reading or writing to the alias is equivalent to performing the operation directly on the alias target.

View the Alias Example projectSave

The simplified Button element below uses aliases to add a label property and an activated event to its interface.

From Button
Rectangle {
    alias label: label.text
    alias activated: tapGesture.tapped

    Text as label {
        text: "Button"
    }

    with Gesture.Tap as tapGesture {}
}

Once the aliases are in place, we can use them to configure the Button, and be notified of when it is tapped.

Button {
    label: "Tap Me!"
    on activated {
        System.log("Tapped!")
    }
}

Aliases can target almost anything - properties, events, functions and even entire objects. For example, instead of just aliasing the label's text, we could expose the entire internal Text object itself.

From Button
Rectangle {
    alias label: label
    alias activated: tapGesture.tapped

    Text as label {
        text: "Button"
    }

    with Gesture.Tap as tapGesture {}
}

This gives us full access to the internal object, allowing us to change any property we want. Try modifying the Alias Example project to work like this.

Button {
    label.text: "Tap Me!"
    label.font: "San Francisco Italic"
}

Static properties

Most properties, functions and events are instance members. That is, each instance of an element has a separate copy of each.

For example, a property stores a distinct value for each instance of an element. A function called on a specific element instance has access to all these property values.

There is a second class of properties, functions and events known as element, or "static", members. In this case, there is only a single copy of their value which is shared across the entire application. Element members can be thought of as "global" or "shared" members.

For example, you would use an instance property to store the number of times a specific Button had been clicked, but you would require a static member to store the number of times all the Buttons in your application had been clicked.

Static members are declared just like regular members, but preceded by the static modifier keyword. To access a static member you use the element name directly, rather that use an element instance reference.

The following example shows how to use a static property.

From Button
Rectangle as root {
    static property Int totalClicks

    // ... Button code...

    on clicked {
        // Correct - increment element property
        Button.totalClicks++

        // Error - not an instance property
        root.totalClicks++
    }
}

Declaring and emitting a static event is very similar to a regular event as shown below.

From Button
Rectangle as root {
    static event buttonClicked(Button instance)

    // ... Button code...

    on clicked {
        Button.buttonClicked(this)
    }
}

As static events aren't associated with a single instance, you can add an event handler for a static events anywhere in your project. You add static event handlers at the root level of a file, like this:

Item {
    // ...code...
}

on Button.buttonClicked(instance) {
    System.log("Button {1} clicked!".arg(instance.label))
}

As a static function is not associated with any specific instance of an element it can't access any instance properties directly. Instead it only has access to other static members and any values passed to it as parameters.

Animation

There are three main ways to achieve animation in Zing - immediate animations, constructed animations and state transition animations. This section will describe immediate animations and animation elements. You can learn more about state transition animations in the State Machines chapter.

Immediate

You can animate a property using the Animation.tween() method. Animation.tween() is suitable for simple animations where you are animating a property to a new value.

The Animation.tween() method takes two arguments. The first is the property to tween, and the second is the value to tween to. The following example tween's the rectangle to a new color each time you tap it.

Rectangle as rect {
    with Gesture.Tap {
        on tapped {
            var color = Color.random()
            Animation.tween(rect.color, color)
        }
    }
}

Tweening works with any property that is a tweenable type - either Int, Float or Color. You can add a delay to the tween, or specify the duration and easing curve using optional parameters. Learn more from the Animation.tween() reference documentation.

Constructed

Touch

You use gesture recognizers to add touch input to your Zing prototypes. Gesture recognizers translate raw, multi-touch input sequences into meaningful gestures like tap, pan and pinch.

Zing includes builtin support for eight different gestures.

Gesture recognizers are not themselves items. Instead, they are a special kind of element called an "extension" element. Extension elements are used to add or modify the behavior of another kind of element. Extension elements work just like other elements, but are applied to their container using the "with" keyword.

Rectangle as rect {
    with Gesture.Tap {
        on tapped {
            rect.color = Color.random()
        }
    }
}

You can apply a gesture recognizer element to any item in your prototype. The item you apply to recognizer to defines the bounds that the gesture will be recognized within.

By themselves the elements simply recognize the gesture, and fire events for your code to handle. It is up to you to decide what happens when the events fire.

There are two classes of gestures, discrete and continuous. A discrete gesture, such as Gesture.Tap or Gesture.Swipe, occurs but once in a multi-touch sequence and results in a single event being emitted. However, when a continuous gesture, such as Gesture.Pan or Gesture.Pinch, is recognized an event is fired for each incremental change until the gesture concludes.

You can add multiple gestures and they will resolve the touch input appropriately. The following example adds an additional right swipe gesture to reset the rectangle to its initial #lightblue color.

Rectangle as rect {
    with Gesture.Tap {
        on tapped {
            rect.color = Color.random()
        }
    }

    with Gesture.Swipe {
        on swiped {
            rect.color = #lightblue
       }
    }
}

Custom gestures

Most touch interactions can be handled by combining the builtin gesture recognizers. These recognizers are well tuned to match the gesture behavior users already expect.

However, if needed you can implement your own custom single- and multi-touch gestures.

Gesture.MultiTouch provides direct access to the raw, multi-touch input and allows you to implement any gesture you can imagine. All of the builtin gestures can be implemented using Gesture.MultiTouch.

Gesture.SingleTouch provides an easier interface for implementing single touch gestures. Of course, single touch gestures can also be implemented using Gesture.MultiTouch directly.

Interactions

Interactions are gestures that also perform an action. For example, the Interaction.Button interaction fades its container item when it is pressed. Interactions allow you to prototype more rapidly, but may not provide the exact behavior you want.

Views

Most prototypes will need to show more than can be fit into a single view.

Conceptually your prototype is a stack of one or more fullscreen views.

You add a new view to the stack by pushing it or by presenting it.

Pushing

Pushing a view navigates from the current view to the new view. You push a view with the View.push() method like this:

Apple.Button {
    label: "Push Page 2"
    on activated {
        View.push(Page2)
    }
}

Pushing is usually used to navigate a view heirarchy, and creates a "back" relationship between the pushed view and the pushing view. Pushed views are always maximized and replace the previous view.

As shown above, when the pushed views have Apple.NavigationBar headers, a back button and an appropriate header transition is added as expected.

Presenting

Presenting a view displays it modally. You present a new view with the View.present() method like this:

Apple.Button {
    label: "Present Page 2"
    on activated {
        View.present(Page2)
    }
}

The difference between pushing and presenting a view is subtle. In addition to using a different default transition, presented views do not have an automatic back button, so must include some way to dismiss the view, such as a done button.

Presented views are not always maxized. By setting the View.presentationStyle extension property on the view, you can choose how the view is presented. The following example shows the FormSheet presentation style. You can see all the presentation styles in the View.PresentationStyle documentation.

Apple.Button {
    label: "Present Page 2"
    on activated {
        View.present(new Page2 {
            View.presentationStyle: View.PresentationStyle.FormSheet
        })
    }
}

Dismissing

You dismiss a view with the View.dismiss() method like this:

Apple.Button {
    systemItem: Apple.Button.SystemItem.Done
    on activated {
        View.dismiss(root)
    }
}

You pass the root item in the view to dismiss as a parameter to the dismiss() method. If an item that wasn't push()'d or present()'d is passed, it will have no effect, and Zing will log a message explaining the mistake.

Logic code

A Zing file is not "run" from top to bottom in the same way as a traditional programming language like JavaScript. Instead, a Zing file is an element declaration, more akin to a class declaration in other languages.

Zing is event driven. You handle events, such as user input, timers or special system events like "start", to execute your prototype's logic code.

Comments

Zing uses C-style comments. Comments begin with with two forward slash characters and continue until the end of the current line.

// This is a single-line comment
var a = 10 // They can also start mid-line,

You can use the following shortcut to quickly comment and uncomment the current line (or lines if multiple lines are selected).

Studio shortcut

⌘/Comment or uncomment code lines

Variables

Variables must be declared before they are used. You can declare variables using the var keyword. Here's an example of two declarations:

var typeOfAnimal = "elephant"
Int numberOfLegs = 4

Every variable has a type, which is set when you declare the it. Only values compatible with the type can be stored in that variable. For example, you can't store the number 10 in a String variable. This is different from dynamically typed languages like JavaScript.

There are two ways to specify a variable's type - explicitly like the "numberOfLegs" variable, or implicitly using the var keyword. Variables declared with the var keyword, like "typeOfAnimal", automatically infer their type from their initial value. You can see the inferred type in Zing Studio by positioning the cursor inside the variable name in the editor.

Variable names must begin with a lowercase letter, but can otherwise consist of letters, numbers and underscores.

Variables in Zing are "block scoped". If you declare a variable within a block, you cannot use it outside that block.

if pinCodeCorrect {
    var secret = getSecret()
}
System.log(secret) // Error: Variable is not accessible here

However, you can use variables declared outside a block inside the block.

var secret = "Unknown"
if pinCodeCorrect {
    secret = getSecret()
}
System.log(secret) // Logs the secret, or "Unknown"

Value Types

Value types directly contain values. Assigning one value type variable to another copies the contained value. Modifying the value in the first variable does not affect the second. Value types are used to represent basic data, like a string or number, that doesn't actually "do" anything itself.

The following example shows that modifying the value in vector2 does not affect the value in vector1.

Vec2 vector1 = Vec2(10, 11)
Vec2 vector2 = vector1

vector2.x = 19

System.log(vector1, vector2)
// Vec2(10, 11) Vec2(19 11)

Value types always have a value. If a value hasn't been explicitly set, they use their default value. For example, the default value of an Int is 0, and the default value of a String is the empty string "".

Element Types

Most types in Zing are elements types, like Item or Http. You have already seen how you use the Zing syntax to create element instances.

Variables with an element type store references to their element instance, while variables of value types directly contain their data. With element types, two variables can reference the same instance; therefore, operations on one variable can affect the instance referenced by the other variable. With value types, each variable has its own copy of the data, and it is not possible for operations on one variable to affect the other.

As the following example shows, when you assign an element type variable, it changes the element the variable refers to. In this case, it changes the reference in variable2 from item2 to item1.

Item as item1 { x: 32 }
Item as item2 { x: 1982 }

function example() {
    Item variable1 = item1
    Item variable2 = item2

    System.log(variable1.x, variable2.x) // 32 1982

    variable2 = variable1
    System.log(variable1.x, variable2.x) // 32 32

    variable1.x = 33
    System.log(variable1.x, variable2.x) // 33 33
}

An element type variable can also be "null", which means that it doesn't refer to any element instance. Logically null is also the default value for an element type variable that hasn't otherwise been asigned a value.

You can assign, or compare against, the special null value to set or test whether a variable is null.

Item variable1
Item variable2 = item1

System.log(variable1 == null) // true
System.log(variable2 == null) // false

variable2 = null
System.log(variable2 == null) // true

Basic types

All Zing apps will work with the simplest units of data: numbers, strings, boolean values, arrays and the like. More complex types are all built out of these primitive types.

All primitives in Zing are value types.

Booleans

The most basic datatype is the simple boolean true/false value.

Bool isDone = true
Bool fastEnough = false

Bool Reference

Numbers

Zing contains two number types - one for integer values and another for floating point values.

Integers can contain signed, 32-bit whole numbers.

Int numberOfLegs = 2
Int temperature = -40

Int Reference

Floats can store signed, 64-bit real numbers. Every Int can also be stored in a Float.

Float pi = 3.14159
Float temperature = -40

Float Reference

Strings

Strings are used to store textual data. Strings in Zing are always surrounded by double quotes.

String buzz = "Get your ass to Mars!"

String Reference

Arrays

An array is a generic container type that can hold a list of other values.

Array types can be written in one of two ways. In the first you use the type of the array values followed by "[]", and in the second you use a generic array type. Both methods give the equivalent array type.

Int[] numbers = [1, 2, 3]
Array<Int> numbers2 = [1, 2, 3]

Array Reference

Dictionaries

A dictionary is a generic container that maps one value, called the key, to another another value.

Dictionary<String, Int> legs = [
    "Snake": 0,
    "Emu": 2,
    "Dog": 4,
    "Centipede": 354
]

Dictionary Reference

Enums

Enums, short for enumerations, are a class of types that can store one of a fixed set of named options. For example, the Text.Alignment enum can be either Left, Center, Right or Justify.

Text.Alignment alignment = Text.Alignment.Center

Enum Reference

Scope

Scope controls how identifier names are resolved in Zing.

Variables

Variables in Zing must be declared before they are used.

System.log(value) // Error: Variable is not accessible here

var value = 10
System.log(value) // Logs "10"

If you declare a variable within a block, you cannot use it outside that block.

if pinCodeCorrect {
    var secret = getSecret()
}
System.log(secret) // Error: Variable is not accessible here

However, you can use variables declared outside a block inside the block.

var secret = "Unknown"
if pinCodeCorrect {
    secret = getSecret()
}
System.log(secret) // Logs the secret, or "Unknown"

Element identifiers

To access the properties of elements in a Zing file, you can give them unique names. A valid element identifier, just like a variable name, must begin with a lowercase letter and contain only letters, numbers and underscores.

An element identifier is declared using the as syntax shown below.

ElementName as identifier {
    // initializer...
}

Here we use an element identifier to ensure two rectangles have the same size and color.

Rectangle as mainRectangle {
    width: 50
    height: 50
    color: #orange
}
Rectangle {
    width: mainRectangle.width
    height: mainRectangle.height
    color: mainRectangle.color
}

this

"this" refers to the element containing the expression. All property expressions and functions have a statically defined "this" object reference that is determined at declaration time.

Rectangle {
   width: this.height
   borderWidth: this.width / 4

   function area() -> Float {
       return this.width * this.height
   }
}

In the preceding example, all the "this" uses reference the Rectangle element.

parent

"parent" refers to the parent of the element containing the expression. In the following example, all uses of "parent" reference the outer Item element.

Item {
    Rectangle {
       width: parent.height
       borderWidth: parent.width / 4

       function outerArea() -> Float {
           return parent.width * parent.height
       }
    } 
}

Unlike "this", not all expressions have a defined parent. Toplevel elements, like that shown below, have no parent and using the "parent" identifier will result in an error.

Rectangle {
    // Error, parent is not defined
    width: parent.width
}

Implicit this

Property expressions and functions frequently access the properties of the element containing them. As it is extremely tedious to constantly preface these accesses with the this identifier, an expression has unqualified access to the properties of its this object whose names do not conflict with other element identifiers in the file.

The following example accesses the height property, without using the this identifier.

Rectangle {
    // width: this.height
    width: height
}

If the file contains an element identifier with the same name as the property, it will take precedence. In these cases, you can still access the property by explicitly using the this identifier.

Rectangle {
    // Error, height refers to the element below.
    // Must explicitly use "this.height"
    width: height
}

Item as height { }

Operators

An operator is a special symbol that you use to check, change or combine values. For example, the addition operator "+" adds two numbers.

10 + 7

The values that operators affect are called operands. In the example above, the + symbol is the operator, and its two operands are the values 10 and 7.

Operators are unary, binary or ternary depending on whether they require 1, 2 or three operands.

Assignment operator

The assignment operator assigns the value on its right hand side to the variable or property on its left hand side.

variable = 10
root.width = 102

The assignment operator does not return a value.

Arithmetic operators

Zing supports the four standard arithmetic operators for number types.

1 + 2     // equals 3
5 - 3     // equals 2
2 * 3     // equals 6
10 / 4    // equals 2.5

When you add, subtract or multiply two Int values, the result is always an Int, otherwise it is a Float. The result of a division is always a Float.

The addition operator is also supported for String concatenation.

"hello, " + "world"  // equals "hello, world"

Remainder operator

The remainder operator finds the largest whole number of multiples of the first operand that will fit into the second operand, and returns the value that is left over.

9 % 4     // equals 1

The remainder operator calculates the remainder term in the following equation:

left operand == right operand * multiplier + remainder

The remainder equation shows how the operator works for various combinations of positive and negative numbers.

 9 %  4     // equals  1  (9 ==  4 *  2 +  1)
-9 %  4     // equals -1  (9 ==  4 * -2 + -1)
 9 % -4     // equals  1  (9 == -4 * -2 +  1)
-9 % -4     // equals -1  (9 == -4 *  2 + -1)

If both the left and right operands are Ints, the result of the remainder operator is also an Int. Otherwise it is a Float.

Power operator

The power operator returns the left operand raised to the power of the right operand.

3 ^ 2     // equals 9
2 ^ 0.5   // equals 1.4142

The result of the power operator is always a Float.

Unary minus operator

The sign of a numeric value can by toggled using the unary minus operator.

var three = 3
var minusThree = -three
var plusThree = -minusThree

The result of the unary minus operator has the same type as its operand.

Unary plus operator

The unary plus operator returns the value it operators on without any change. Its only use is to provide symmetry in your code when also using the unary minus operator.

var minusSix = -6
var alsoMinusSix = +minusSix  // alsoMinusSix equals -6

The result of the unary plus operator has the same type as its operand.

Compound assignment operators

Compound assignment operators combine an assignment with an arithmetic operation.

a = 2
a += 3   // a is now equal to 5

There is a corresponding compound assignment operator for each binary arithmetic operator.

a += 3   // equivalent to a = a + 3
a -= 2   // equivalent to a = a - 2
a *= 4   // equivalent to a = a * 4
a /= 9   // equivalent to a = a / 9
a ^= 5   // equivalent to a = a ^ 5
a %= 7   // equivalent to a = a % 7

Compound assignment operators do not return a value.

Comparison operators

The comparison operators compare two operands, and return the boolean value true when the comparison is true and false when it is not.

Equality comparisons

The equality comparisons compare whether one value is exactly equal to another.

10 == 10   // True because 10 is equal to 10
10 != 11   // True because 10 is not equal to 11

The equality comparisons can compare any two operands of the same type.

Relational comparisons

The relational comparisons compare whether one value is less than or greater than another.

10 < 11    // True because 10 is less than 11
11 > 10    // True because 11 is greater than 10
10 <= 10   // True because 10 is less than or equal to 10
10 >= 11   // False because 10 is not greater than or equal to 11

The relational comparisons can compare two numeric operands or two string operands.

Ternary conditional operator

The ternary conditional operator is a special operator with three parts, which takes the following form

question ? answer1 : answer2

It is a shortcut for evaluating one of two expressions based on whether question is true or false. If question is true, it evaluates answer1 and returns its value; otherwise, it evaluates answer2 and returns its value.

The ternary operator is shorthand for the following pseudo code

if question {
    answer1
} else {
    answer2
}

The ternary conditional operator provides an efficient shorthand for deciding which of two expressions to consider. However, the ternary conditional operator should be used with care as it can lead to hard-to-read code if overused.

The code below demonstrates how you might use the ternary conditional operator in a property expression to set an item's height.

height: 60 + (header.visible ? header.height : 0)

Range operators

Zing includes two range operators, which are most useful for generating a numeric sequence as input to a for statement.

Closed range operator

The closed range operator creates a range spanning from the value of the left operand to the value of the right operand, inclusive of both values.

for var index in 1 ... 5 {
    System.log(index, "times 5 is", index * 5)
}

// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

Both operands must be Ints.

Half-open range operator

The half-open range operator creates a range that runs from the value of the left operand to the value of the right operand, but does include the value of the right operand. It is said to be half open as it contains the value of the first operand, but not the value of the second.

Half-open ranges are particularly useful when you work with zero-based lists such as arrays, where it is useful to count up to (but not including) the length of the list.

var names = ["Bob", "Clair", "David", "Sarah"]
for var index in 0 ..< names.length {
    System.log("Person", index + 1, "is called", names[index])
}

// Person 1 is called Bob
// Person 2 is called Clair
// Person 3 is called David
// Person 4 is called Sarah

Both operands must be Ints.

Logical operators

Logical operators modify or combine the boolean logic values true and false. They are used most often as input to if and while statements.

Logical NOT operator

The NOT operator inverts a boolean value, such that true becomes false and false becomes true. The NOT operator appears immediately before the value it operates on.

var allowedEntry = false
if !allowedEntry {
    System.log("ACCESS DENIED")
}

// Logs "ACCESS DENIED"

As the NOT operator is a prefix operator, the production "if !allowedEntriy" is often read as "if not allowed entry".

Logical AND operator

The AND operator returns true if both its left and right operands are true, and false otherwise.

var hasDoorCode = true
var passedRetinaScan = false
if hasDoorCode && passedRetinaScan {
    System.log("Welcome!")
} else {
    System.log("ACCESS DENIED")
}

// Logs "ACCESS DENIED"

The AND operator short-circuits the evaluation of the right hand expression.

Logical OR operator

The OR operator returns true if either its left or right operands are true, and false otherwise.

var hasDoorKey = false
var hasPassword = true
if hasDoorKey || hasPassword {
    System.log("Welcome!")
} else {
    System.log("ACCESS DENIED")
}

// Logs "Welcome!"

The OR operator short-circuits the evaluation of the right hand expression.

Combining logical operators

You can combine multiple logical operators to create longer compound expressions.

if hasDoorCode && passedRetinaScan || hasDoorKey || hasPassword {
    System.log("Welcome!")
} else {
    System.log("ACCESS DENIED")
}

// Logs "Welcome!"

This example uses multiple && and || operators to create a longer compound expression. However, the && and || operators still operate on only two values, so this is actually three smaller expressions chained together.

The expression shown above can be read as "if we’ve entered the correct door code and passed the retina scan, or if we have a valid door key, or if we know the emergency password, then allow access".

It is sometimes useful to include parentheses when they are not strictly needed, to make the intention of a complex expression easier to read.

if (hasDoorCode && passedRetinaScan) || hasDoorKey || hasPassword {
    System.log("Welcome!")
} else {
    System.log("ACCESS DENIED")
}

// Logs "Welcome!"

Control flow

Control flow statements are used to control the flow of execution in a program. There are three types of control flow statements in Zing: loop statements, branch statements, and control transfer statements.

For-in loop

The for-in loop allows you to execute a block of statements multiple times, once for each value in a sequence, such as a range of numbers, or the values in an array.

for var index in 1...3 {
    System.log("index is", index)
}
// index is 1
// index is 2
// index is 3

The for-in loop looks like this:

for initializer in expression {
    statements
}

The source expression is only executed once, at the beginning of the loop. Modifications made to the source during the loop will not effect it.

The loop will execute once for each value in the source sequence. If the sequence is empty, the loop will not run at all and execution will continue following the closing brace. For each loop, the sequence value will be assigned to the initializer expression, and then the body statements will be executed.

This example iterates over the names array, and greets each name:

var names = ["Aaron", "Anna", "Sam", "Sarah"]
for var name in names {
    System.log("Hello,", name)
}
// Hello, Aaron
// Hello, Anna
// Hello, Sam
// Hello, Sarah

Zing also supports C-style for loops.

While loop

var result = 1
var factor = 4
while factor > 1 {
    result *= factor
    --factor
    System.log("current:", result)
}
// current: 4
// current: 12
// current: 24
while condition {
    statements
}

If statement

if condition {
    statements
} else if condition {
    statements
} else {
    statements
}

Switch statement

A switch statement is used to compare the result of an expression against a collection of possible values.

var animal = "horse"
switch animal {
    case "human", "emu" {
        System.log("This animal has 2 legs")
    }
    case "dog", "horse", "cow" {
        System.log("This animal has 4 legs")
    }
    case "millipede" {
        System.log("A millipede has up to 750 legs!")
    }
    default {
        System.log("I don't know how many legs this animal has")
    }
}
// This animal has 4 legs

Switch statements consist of an expression, and a collection of case clauses like this:

switch expression {
    case value 1 {
        statements for 1
    }
    case value 2 , value 3 {
        statements for 2 or 3
    }
    default {
        statements for other
    }
}

The switch expression is first evaluated, and the result is compared to to each case clause value in order. If a matching value is found, the body statements for that case are run. After the body has run, the switch statement finishes and execution continues after the final closing brace.

If a matching case clause is not found, the default clause, if present, is executed instead.

Unlike switch statements in languages like C and Java, case blocks in Zing do not implicitly fall through by default and consequently do not require a terminating break statement. If you've never used a programming language that does require an explicit break, just pretend this section doesn't exist.

If you need C-style fallthrough behavior, you can use the fallthrough statement. When the fallthrough statement is run, execution jumps immediately to the start of the next case or default block and runs the case's body statements.

The example below uses fallthrough to create a textual description of a number:

var integerToDescribe = 5
var description = "The number " + integerToDescribe + " is"
switch integerToDescribe {
    case 2, 3, 5, 7, 11, 13, 17, 19 {
        description += " a prime number, and also"
        fallthrough
    }
    default {
        description += " an integer."
    }
}
System.log(description)
// prints "The number 5 is a prime number, and also an integer."

Continue

A continue statement can only appear within the body of a for-in, while or do-while loop. The continue instructs the loop to skip the remaining body statements, and start again at the beginning of the next iteration.

The following example uses the continue statement to skip all numbers that are not an exact multiple of three:

var input = [15, 32, 21, 12, 92]
for var number in input {
    if number % 3 {
        continue
    }

    var multiple = number / 3
    System.log(multiple + " * 3 = " + number)
}
// 5 * 3 = 15
// 7 * 3 = 21
// 4 * 3 = 12

Break

A break statement can only appear within the body of a for-in, while or do-while loop or within a case block of a switch statement. The break ends the execution of its container statement immediately. Following a break statement, execution resumes directly after the enclosing switch or loop statement.

This example iterates over an array, and terminates the loop when the first multiple of 7 is found:

var input = [12, 16, 43, 56, 27]
for var number in input {
    if (number % 7) == 0 {
        System.log(number + " is the first multiple of 7")
        break
    }
}
// 56 is the first multiple of 7

Creating elements

You can create new element instances dynamically using the new operator.

new Element name {
    initializer
}

Each call to the new operator returns a single new instance of the specified element. The initializer can be used to set initial property values, or omitted if not required.

Zing is garbage collected, so new'd instances will be destroyed as soon as there are no longer any references to them. Consequently, to ensure that an instance continues to exist after the code that created it terminates, it must be stored in a property, added to an array or, in the case of a visual Item, have its container set.

The following example creates four rectangles at random horizontal locations each time it is called. Notice how it clears the root children before each run, otherwise the rectangles would just accumulate indefinitely.

function createRectangles() {
    root.clearChildren();

    for (var ii = 0; ii < 4; ++ii) {
        var xPos = Math.random(0, 150)

        var r = new Rectangle {
            x: xPos; y: ii * 50
            width: 50; height: 50
            color: root.boxColor
        }
        r.setContainer(root)
    }
}

This example also demonstrates variable capturing. The rectangle's x and y properties are initialized with expressions that "capture" the value of the variables xPos and ii. This is called capturing as the value for each is determined at the moment the new rectangle is created, and does not change thereafter. Consequently, when the value of the ii variable changes as the loop continues, it does not effect the values already captured.

Asynchronous code

Zing includes asynchronous APIs for functionality that might take an extended time to complete, such as network activity.

Task

A Task is a generic value type that represents an ongoing, asynchronous operation that will complete in the future.

The Task's isReady property is used to determine if the asynchronous operation has completed, and the Task's value property contains the result of the operation when it has.

The Task type is a generic container type. This means that it can be specialized to hold different result types. For example, you can have a Task that holds an Int result value, or a Task that holds a HttpResponse result value.

You declare a Task variable in the same way as other generic types, such as arrays and dictionaries, using the angle bracket syntax to specify the type that it holds. For clarity we specify all the types explicitly in the following examples, but in real code you would usually use var and let type inference do all the work.

Task<Http.Response> task = Http.get("http://www.google.com")
// Don't worry about all the typing, you can also write:
// var task = ...

if (task.isReady) {
    Http.Response response = task.value
    System.log(response.body)
}

Unfortunately, this example is pretty useless as the Task returned by the HTTP get method will never be ready right away and none of the precious code inside the if body will ever run.

To make it useful, we use the await operator to wait until the task completes and the result is available.

Task<Http.Response> task = Http.get("http://www.google.com")

await task

if (task.isReady) { // Always true, thanks to the await
    Http.Response response = task.value
    System.log(response.body)
}

The await operator doesn't just wait for the Task to complete, as a convenience it also returns its value. This allows us to simplify the code by assigning from the result of the await operator directly, like this:

Task<Http.Response> task = Http.get("http://www.google.com")

Http.Response response = await task
System.log(response.body)

async functions

The previous section glossed over exactly what happens when the await operator waits.

When an await is encountered, the state of the running function is captured as a continuation on the Task and the function returns to its caller immediately. A continuation is a special representation of the execution state of the function which allows it to be suspended and resumed later. When the awaited Task completes, the continuation thaws out the function and resumes running it like nothing happened, only now the Task is ready and its value available.

When you call a function that awaits, you are starting an ongoing operation that will complete in the future. It should be no surprise that when the awaiting function returns to its caller, it returns a Task that represents this new operation. This returned Task will become ready only after the awaiting function has resumed and ultimately completed its execution.

Imagine that we wanted to write a convenience function that returns the body of a HTTP URL.

async function fetch(Url url) -> String
{
    Http.Response response = await Http.get(url)
    return response.body
}

The fetch function is declared like any other function - it takes a Url as an argument and returns a String. The body of the function is also straightforward, and simply creates a Http object, gets the Url and returns the response body.

The one difference between the fetch function and a regular function is the presence of the async modifier. The async keyword indicates that the function may await, and automatically wraps the return value in a Task. Due to the async modifier, the actual return type of the fetch method is Task.

The fetchAll function uses fetch to retrieve three URLs in parallel.

async function fetchAll()
{
    Task<String> google = fetch("http://www.google.com")
    Task<String> bing = fetch("http://www.bing.com")
    Task<String> yahoo = fetch("http://www.yahoo.com")

    String[] bodies = await [google, bing, yahoo]

    for (String body in bodies) {
        System.log(body)
    }
}

The method starts by calling fetch for each URL. Remember that when fetch executes the await operator it does not block, but simply suspends the fetch function and immediately returns a new Task. This allows you to parallelize work - in this example all three gets are running simultaneously.

Before it can access their values, the fetchAll method must await on the returned Tasks. Just as awaiting on a single Task will return its value once it becomes ready, awaiting on an array of Tasks will not continue until all of the Tasks are ready and will return an array containing all of their values.

You may have noticed that the fetchAll function also has the async modifier. All code that awaits and returns to a caller must be marked as an async function. As the fetchAll method doesn't compute and return a specific value, the async modifier causes it to return an unparameterized Task. An unparameterized Task has an isReady property, but does not have a value. This allows you to await for it to complete, even though no actual value is produced as a result.

Item {
    with Gesture.Tap {
        on tapped {
            Task fetch = fetchAll()

            // There is no value returned by this await!
            await fetch
            System.log("All done!")
        }

        async function fetchAll() {
            // ...
        }
    }
}

Networking

State machines

State machines are a powerful way of visually describing the behavior of your project. As a formal technique for modelling complex systems, the ideas behind state machines have existed for decades. State machines in Zing are based on a category of hierarchical state machines known as Harel statecharts.

States and transitions

A state machine is composed of two disinct parts - states and transitions.

Logically, a system is always in one of multiple distinct "states". For example, a simple push button is either pressed or not pressed. At the most basic level, a state machine simply enumerates all of these states explicitly. The list of states in the machine is exhaustive. That is, the machine is always in one of these states - pressed or not pressed. There is no third option!

A state machine always begins in the default state, shown in the diagram by a small marker in the top left corner of the state box. "Transitions" tell the machine when to change from one state to another. For example, we need a transition to tell our simple push button machine when to move from the NotPressed to the Pressed states, and another transition to tell it when to move back.

Every transition has a source and target state, and a transition criteria. When the transition criteria is met, the machine "takes" the transition and changes to the target state. You can see a transition's criteria in the examples by hovering over the disclosure diamonds in the state machine diagrams.

Taking a transition is the only way a state machine will change state. There is no way to "force" a machine into a state.

State machines grow gracefully through the addition of new states. For example, we will add a new behavior to the simple push button such that it enlarges slightly when it has been pressed for an extended period of time.

Property expressions

In the simple push button example, the background color of the button is light gray when it is pressed or held and white when it is not. Likewise the button is scaled slightly when it is in the help state.

With a state machine, we can declare a different property expressions for each state and Zing will ensure that it is updated whenever necessary. Declaring the property expression for a specific state is similar to a regular declaration, except you append the special @StateName modifier to the property name like this:

Rectangle {
    color: #white
    [email protected]: #dedede
    [email protected]: #dedede
    [email protected]: 1.15
}

When there isn't an explicit declaration for a state, as is the case with the color property and the NotPressed state, the regular property value - in this case white - is used.

Although the property expressions shown are only simple literal values, you can use any expression just like you can with a non-state property declaration.

Nested states

The state machines seen so far have been a type of linear state machines known as finite state machines. Finite state machines are extended by adding new top-level states to the machine for each possible configuration they can represent.

The downside of this approach can be seen in our three state button. We have had to duplicate both the release transition and the color property declaration in both the Pressed and Held states. As Held is an entirely new state we will have to manually duplicate any additional behavior we want it to share with the Pressed state both now and in the future.

Conceptually, the Held state is not really a separate state. Instead it is a refinement of the Pressed state. Whenever the button is being held, it is also simultaneously being pressed. We can model "refinement" relationships like this by nested state machines.

Nesting state machines allow you to embed a complete state machine inside an existing state. The nested state machine is evaluated whenever the parent state is active.

Rectangle {
    color: #white
    [email protected]: #dedede
    [email protected]: 1.15
}

Parallel states

Rectangle {
    color: #white
    [email protected]: #dedede
    [email protected]: 1.15
    Text {
        [email protected]: false
        text: "Press and Hold Me!"
    }
}

Pseudo States

It is often useful to modify property expressions based on the category of device your app is running on. To this end Zing includes a special device() directive that can be used in place of a regular state name to determine current device type. The device directive includes a comma separated list of device categories and is true whenever the current device meets one or more of those designations.

The following example sets the margin property differently depending on whether the device is an iPad, or an iPhone with a 4", 4.7" or 5" screen.

property Float margin: 5

[email protected](iPad): 10
[email protected](iPhoneScreen4): 15
[email protected](iPhoneScreen47,iPhoneScreen55): 20

The complete list of valid device designations is below.

Share and export

You can share your Zing projects with others in two different ways.

Zing Share allows you to share your project online, where it can be opened in either Zing Studio on the Mac, or directly from the Zing Preview app on iOS or Android.

You can also export your Zing project to either Xcode or Android Studio and compile it into a native app for distribution through the Apple App Store, or Google Play Store.

Zing Share

Zing Share publishes your project online. Shared projects can be opened by anyone in Zing Studio on the Mac, or in the Zing Preview app on iOS or Android. You don't need a Zing subscription to view a share!

To share a project, open Share from the project explorer, and click "Create Share". This will create a new share if the project has not been shared before, or update an existing share if it has.

Once the upload completes, you will be redirected to your share's landing page in Safari. The URL of this page is your share link, and can be given to anyone you want to access your project. The share link will remain the same even if you make updates to the project.

Managing shares

You can manage your your current shares from your account page. If you delete a share, its share link will be invalidated and prevent any subsequent downloads.

If you need to change the share link, just delete and re-share the project and a new, unique share link will be created.

Exporting

You can export your Zing project to native Xcode or Android Studio projects. Xcode is the development environment for iOS devices, and Android Studio the development environment for Android devices.

Exporting allows you to compile your Zing project as a native app, and distribute it through the Apple App Store and Google Play Store.

You require a Zing developer subscription to use exporting.

Creating the exported project

To create a new exported project, open Share from the project explorer, and click the "New Project" button under either Xcode or Android Studio. This will download an Objective-C application template, in the case of Xcode, or a Java application template, in the case of Android Studio, and create a copy for your project.

If you make further changes to your project in Zing Studio, you can update an existing exported project using the "Update Project" button instead. This updates just the Zing component of the Xcode or Android Studio project and leaves any native code or configuration changes you have made to the project intact.

Next Steps

Working with native apps is not always as easy as Zing. You may require additional assistance from an experienced iOS or Android developer in order to submit your application to the Apple App Store or the Google Play Store.