iOS App Development Basics with Swift: Complete Beginner’s Guide

Introduction

Building iOS apps opens access to one of the most lucrative mobile markets. iOS users tend to spend more on apps and in-app purchases. The App Store provides global distribution. Apple’s ecosystem—iPhone, iPad, Apple Watch, Apple TV—offers expansion opportunities beyond the phone.

Swift, Apple’s modern programming language introduced in 2014, has become the standard for iOS development. It combines safety, performance, and expressiveness in ways that make mobile development more productive and enjoyable.

This guide covers everything you need to start building iOS apps with Swift in 2021.

Setting Up Your Development Environment

Setting Up Your Development Environment Infographic

Requirements

iOS development requires a Mac. There is no official way to develop iOS apps on Windows or Linux. Apple’s tools only run on macOS.

You need:

  • A Mac running macOS Catalina (10.15) or later, ideally Big Sur (11.0)
  • Xcode 12, Apple’s integrated development environment
  • An Apple ID (free to create)
  • Optionally, an Apple Developer Program membership ($149 AUD annually) for App Store distribution

Installing Xcode

Xcode is available free from the Mac App Store. The download is approximately 12GB, so plan accordingly.

After installation, open Xcode and accept the license agreement. Xcode will install additional components on first launch.

Verify your installation by creating a new project. Select File > New > Project, choose “App” under iOS, and create a simple project. If it builds and runs in the simulator, your environment is ready.

Understanding Xcode

Xcode combines code editing, interface design, debugging, performance analysis, and device management. The interface is dense but logical once you understand the layout.

The Navigator (left panel) provides file browsing, search, issue tracking, and more. The Editor (centre) shows your code or interface files. The Inspector (right panel) displays context-sensitive properties. The Debug area (bottom) shows console output and debugging tools.

Key sections you will use constantly:

  • Project Navigator: Browse your project files
  • Issue Navigator: View compilation errors and warnings
  • Debug Navigator: Monitor CPU, memory, and performance during debugging
  • Storyboard Editor: Visual interface design
  • Swift Editor: Code editing with autocompletion

Swift Language Fundamentals

Swift Language Fundamentals Infographic

Variables and Constants

Swift distinguishes between variables (mutable) and constants (immutable) using var and let:

var currentScore = 0      // Can change
let maximumScore = 100    // Cannot change

currentScore = 50         // Valid
// maximumScore = 200     // Compiler error

Prefer let unless you specifically need mutation. The compiler optimises constants more aggressively, and immutability prevents accidental changes.

Type Inference and Annotations

Swift infers types from initial values:

let name = "Melbourne"        // Inferred as String
let population = 5000000      // Inferred as Int
let temperature = 28.5        // Inferred as Double

You can explicitly annotate types when needed:

let latitude: Double = -37.8136
let isCapital: Bool = false
var cities: [String] = []

Optionals

Swift handles the absence of values through optionals—variables that might contain a value or might be nil:

var middleName: String? = nil  // Optional String
middleName = "James"           // Now has a value

// Safely unwrap with if-let
if let name = middleName {
    print("Middle name is \(name)")
} else {
    print("No middle name")
}

// Guard statements for early exit
func greet(name: String?) {
    guard let unwrappedName = name else {
        print("No name provided")
        return
    }
    print("Hello, \(unwrappedName)")
}

Optionals eliminate null pointer exceptions common in other languages. The compiler forces you to handle the absence case.

Collections

Swift provides arrays, dictionaries, and sets:

// Arrays - ordered collections
var fruits = ["Apple", "Banana", "Cherry"]
fruits.append("Date")
let firstFruit = fruits[0]  // "Apple"

// Dictionaries - key-value pairs
var scores = ["Alice": 95, "Bob": 87, "Carol": 92]
scores["Dave"] = 88
let aliceScore = scores["Alice"]  // Optional(95)

// Sets - unordered unique values
var uniqueNumbers: Set = [1, 2, 3, 3, 3]  // Contains 1, 2, 3

Functions

Functions in Swift are flexible and expressive:

// Basic function
func greet(person: String) -> String {
    return "Hello, \(person)!"
}

// Multiple parameters with argument labels
func greet(person: String, from hometown: String) -> String {
    return "Hello, \(person) from \(hometown)!"
}
let greeting = greet(person: "Alice", from: "Sydney")

// Default parameters
func greet(person: String, punctuation: String = "!") -> String {
    return "Hello, \(person)\(punctuation)"
}

// Functions as parameters
func performOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}
let sum = performOperation(5, 3, operation: { $0 + $1 })

Closures

Closures are self-contained blocks of functionality:

let numbers = [1, 2, 3, 4, 5]

// Full closure syntax
let doubled = numbers.map({ (number: Int) -> Int in
    return number * 2
})

// Shortened syntax
let tripled = numbers.map { $0 * 3 }

// Filtering
let evenNumbers = numbers.filter { $0 % 2 == 0 }

// Reducing
let total = numbers.reduce(0) { $0 + $1 }

Classes and Structs

Swift provides both classes (reference types) and structs (value types):

// Struct - value type, copied on assignment
struct Point {
    var x: Double
    var y: Double

    func distanceFromOrigin() -> Double {
        return (x * x + y * y).squareRoot()
    }
}

var point1 = Point(x: 3, y: 4)
var point2 = point1  // Creates a copy
point2.x = 10        // Only point2 changes

// Class - reference type, shared on assignment
class Person {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func celebrateBirthday() {
        age += 1
    }
}

let person1 = Person(name: "Alice", age: 30)
let person2 = person1  // References same object
person2.age = 31       // Both see the change

Prefer structs for most data types. Use classes when you need inheritance or reference semantics.

Building Yo

Building Your First App Infographic ur First App

Creating a New Project

Open Xcode and select “Create a new Xcode project”. Choose “App” under iOS. Configure your project:

  • Product Name: Your app’s name (e.g., “TaskTracker”)
  • Team: Your Apple ID or developer team
  • Organisation Identifier: Reverse domain (e.g., “com.yourcompany”)
  • Interface: Storyboard (for this tutorial)
  • Language: Swift

Click Create and choose a location for your project.

Understanding the Project Structure

Xcode creates several files:

  • AppDelegate.swift: Application lifecycle events
  • SceneDelegate.swift: UI lifecycle (iOS 13+)
  • ViewController.swift: Your first view controller
  • Main.storyboard: Visual interface layout
  • Assets.xcassets: Images and colours
  • LaunchScreen.storyboard: App launch screen
  • Info.plist: App configuration

Designing the Interface

Open Main.storyboard. You see a blank view controller—your app’s first screen.

Open the Library (+ button in toolbar) and drag components onto your view:

  1. Drag a Label to the top. Set its text to “My Tasks”
  2. Drag a Table View below it, stretching to fill available space
  3. Drag a Button to the bottom right. Set its title to “Add Task”

Select each element and use the Add New Constraints button (bottom right) to set Auto Layout constraints. For the label, pin it to the top safe area with 20 points margin. For the table view, pin all edges to surrounding elements.

Connecting Interface to Code

Control-drag from interface elements to ViewController.swift to create connections:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var tasks: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
    }

    @IBAction func addTaskTapped(_ sender: UIButton) {
        let alert = UIAlertController(
            title: "New Task",
            message: "Enter task name",
            preferredStyle: .alert
        )

        alert.addTextField { textField in
            textField.placeholder = "Task name"
        }

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
        alert.addAction(UIAlertAction(title: "Add", style: .default) { _ in
            if let taskName = alert.textFields?.first?.text, !taskName.isEmpty {
                self.tasks.append(taskName)
                self.tableView.reloadData()
            }
        })

        present(alert, animated: true)
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell")
            ?? UITableViewCell(style: .default, reuseIdentifier: "TaskCell")
        cell.textLabel?.text = tasks[indexPath.row]
        return cell
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            tasks.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }
    }
}

Running Your App

Press Command+R or click the Run button. Xcode compiles your app and launches it in the iOS Simulator.

Try adding tasks. Swipe left to delete them. You have built a functional iOS app.

Understanding UIKit

View Controllers

View controllers manage a screen of content. Each screen in your app typically has its own view controller. They handle:

  • Loading and laying out views
  • Responding to user interactions
  • Managing data for the screen
  • Coordinating with other view controllers

Common view controller types:

  • UIViewController: Base class for custom screens
  • UINavigationController: Manages a stack of view controllers
  • UITabBarController: Manages tabbed interface
  • UITableViewController: Optimised for table-based screens

Views and Layers

Views are the building blocks of your interface. UIKit provides many built-in views:

  • UILabel: Display text
  • UIButton: Tappable buttons
  • UIImageView: Display images
  • UITextField: Text input
  • UITextView: Multiline text
  • UITableView: Scrollable lists
  • UICollectionView: Grid layouts
  • UIScrollView: Scrollable content

Auto Layout

Auto Layout positions views based on constraints rather than fixed coordinates. This enables interfaces that adapt to different screen sizes and orientations.

Key concepts:

  • Constraints: Rules defining position and size
  • Safe Area: Region that avoids system UI (notch, home indicator)
  • Content Hugging: Resistance to growing
  • Compression Resistance: Resistance to shrinking
// Programmatic constraints
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
    label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
    label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
])

Most apps have multiple screens. UINavigationController manages a stack of view controllers:

// Push a new view controller
let detailVC = DetailViewController()
detailVC.task = selectedTask
navigationController?.pushViewController(detailVC, animated: true)

// Pop back
navigationController?.popViewController(animated: true)

// Present modally
let settingsVC = SettingsViewController()
present(settingsVC, animated: true)

Best Practices for Beginners

Start Simple

Build the simplest version that works. Complexity emerges naturally as you add features. Resist the urge to architect for scale before you have validated your concept.

Read Apple’s Documentation

Apple’s documentation is comprehensive. The Human Interface Guidelines explain design conventions. The Swift Programming Language book is the definitive Swift reference. WWDC videos provide deep dives into frameworks.

Learn the Debugger

Xcode’s debugger is powerful. Set breakpoints by clicking line numbers. Inspect variables in the debug area. Use po (print object) in the console to examine values.

Use Source Control

Git integration is built into Xcode. Commit frequently. Create branches for experiments. Source control protects you from yourself.

Test on Real Devices

The simulator is convenient but imperfect. Performance, memory, and behaviour differ from real devices. Test on physical devices before releasing, especially for camera, GPS, or performance-sensitive features.

Next Steps

This guide covers the basics. iOS development depth comes from practice and exploration.

Recommended next topics:

  • Networking: URLSession for API calls
  • Data Persistence: Core Data or UserDefaults
  • SwiftUI: Apple’s declarative UI framework
  • Combine: Reactive programming framework
  • App Architecture: MVC, MVVM, or coordinator patterns

Build projects. Break things. Read documentation when stuck. The iOS development community is welcoming—ask questions on Stack Overflow or the Apple Developer Forums.

Your first app will not be perfect. Neither will your tenth. What matters is shipping, learning, and improving. Start building today.