This page reviews how arrays work in Swift — how to declare them, how to access their elements, and how to iterate over them. It then connects those ideas to the declarative syntax you use every day in SwiftUI.

Declaring an Array

Swift infers the element type from the values you provide, so both of these are equivalent:

// Type inferred
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
 
// Type explicitly annotated
var planets: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]

An empty array requires an explicit type, since Swift has nothing to infer from:

var planets: [String] = []

Accessing Elements by Index

Arrays in Swift are zero-indexed: the first element is at index 0, and the last is at index count - 1.

var planets: [String] = ["Mercury", "Venus", "Earth", "Mars",
                          "Jupiter", "Saturn", "Uranus", "Neptune"]
 
print(planets[0])               // Mercury
print(planets[3])               // Mars
print(planets[planets.count - 1])  // Neptune

Swift provides convenience properties for the first and last elements that return optionals, protecting against the case of an empty array:

print(planets.first ?? "No planets")   // Mercury
print(planets.last ?? "No planets")    // Neptune
print(planets.count)                   // 8
print(planets.isEmpty)                 // false

Important: Accessing an index that does not exist — for example, planets[10] when the array only has 8 elements — will cause a runtime crash. Always guard against out-of-bounds access when the index is not known in advance.

Iterating with a for-in Loop

When you need to work with every element and do not need the index, iterating directly over values is the clearest approach:

for planet in planets {
    print(planet)
}
 
// Output:
// Mercury
// Venus
// Earth
// Mars
// Jupiter
// Saturn
// Uranus
// Neptune

Iterating with Indices

When you need to know the position of each element — for example, to display a numbered list or to modify elements in place — iterate over the array’s indices property instead:

for index in planets.indices {
    print("\(index + 1). \(planets[index])")
}
 
// Output:
// 1. Mercury
// 2. Venus
// 3. Earth
// 4. Mars
// 5. Jupiter
// 6. Saturn
// 7. Uranus
// 8. Neptune

Iterating over indices is the right choice when you need both the element and its position — particularly when you need to mutate elements in place, since planets[index] gives you a direct reference to the stored element rather than a copy.

NOTE

Swift also provides enumerated(), which pairs each element with a counter in a single expression. This is a clean option when you need both values but do not need to mutate the array:

for (index, planet) in planets.enumerated() {
    print("\(index + 1). \(planet)")
}

The difference from indices is subtle: enumerated() provides a counter starting at 0 (which you can offset), while indices provides the actual index positions of the array. For standard zero-based arrays these produce the same results, but indices is the safer and more expressive choice when mutation is involved.

Adding and Removing Elements

For reference, the most common mutations:

planets.append("Pluto")                 // Adds to the end
planets.insert("Ceres", at: 4)          // Inserts at a specific index
planets.remove(at: 0)                   // Removes by index
planets.removeLast()                    // Removes the last element

Arrays and User Input

In a command-line program, you can build up an array from user input using readLine() and append():

var favouritePlanets: [String] = []
 
print("Enter three of your favourite planets:")
 
for _ in 1...3 {
    if let input = readLine() {
        favouritePlanets.append(input)
    }
}
 
print("\nYou entered:")
for (index, planet) in favouritePlanets.enumerated() {
    print("\(index + 1). \(planet)")
}

Each call to readLine() blocks execution until the user presses Return, returning the typed text as an optional String. Appending to the array each time builds the collection incrementally.

Arrays in MVVM with SwiftUI

In a SwiftUI app using MVVM, the array lives in the view model as a stored property, and the view iterates over it using ForEach. The key insight is that ForEach is the declarative equivalent of a for-in loop: instead of executing a block of statements for each element, it produces a block of views.

Here is a minimal but complete example using a Planet model.

TIP

Optionally, download this app and try it out.

Model

Planet.swift

import Foundation
 
struct Planet: Identifiable {
 
    // MARK: Stored properties
    let id = UUID()
    let name: String
    let diameter: Int   // kilometres
 
}

Identifiable conformance is required by ForEach so that SwiftUI can track which view corresponds to which element. Using UUID() as the id gives each instance a guaranteed unique identifier.

View Model

PlanetViewModel.swift

import Foundation
import Observation
 
@Observable
class PlanetViewModel {
 
    // MARK: Stored properties
    var planets: [Planet] = [
        Planet(name: "Mercury", diameter: 4_879),
        Planet(name: "Venus",   diameter: 12_104),
        Planet(name: "Earth",   diameter: 12_756),
        Planet(name: "Mars",    diameter: 6_792),
        Planet(name: "Jupiter", diameter: 142_984),
        Planet(name: "Saturn",  diameter: 120_536),
        Planet(name: "Uranus",  diameter: 51_118),
        Planet(name: "Neptune", diameter: 49_528),
    ]
 
}

The @Observable macro means SwiftUI will automatically re-render any view that reads planets whenever the array changes.

View

ContentView.swift

import SwiftUI
 
struct ContentView: View {
 
    // MARK: Stored properties
    @State var viewModel = PlanetViewModel()
 
    // MARK: Computed properties
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.planets) { planet in
                    HStack {
                        Text(planet.name)
                            .font(.headline)
                        Spacer()
                        Text("\(planet.diameter.formatted()) km")
                            .foregroundStyle(.secondary)
                            .font(.subheadline)
                    }
                }
            }
            .navigationTitle("Solar System")
        }
    }
 
}
 
#Preview {
    ContentView()
}

SwiftUI tracks which properties of the view model are read during rendering and only re-renders the view if those specific properties change.

Comparison

It is worth pausing to compare the two iteration patterns side by side:

Imperative — a for-in loop in a command-line program:

for planet in planets {
    print(planet.name)
}

Declarative — ForEach in a SwiftUI view:

ForEach(viewModel.planets) { planet in
    Text(planet.name)
}

Both iterate over every element in the array, binding each element in turn to the loop variable planet. The difference is in what the body produces: the for-in loop executes statements (side effects like print), while ForEach produces views that SwiftUI assembles into a rendered interface.

This is the heart of SwiftUI’s declarative model. You describe what the interface should look like for each element, and SwiftUI handles how and when to render it.

Summary

ConceptSyntaxUse when…
Access by indexarray[i]You need a specific element at a known position
.first / .lastarray.first ?? fallbackYou need the first or last element safely
for-in (values)for item in arrayYou need every element and not its index
for-in (indices)for index in array.indicesYou need the index, or need to mutate elements
enumerated()for (i, item) in array.enumerated()You need both the element and a counter, read-only
ForEach (SwiftUI)ForEach(array) { item in ... }You are building a view for each element in a SwiftUI layout