Back to Blog
Development Tips11 min read

Swift iOS Development Tutorial for Beginners 2026

Complete Swift tutorial for iOS app development. Learn Swift basics, UIKit, SwiftUI, and build your first iPhone app step by step.

Hevcode Team
January 19, 2026

Swift is Apple's powerful and intuitive programming language for iOS, macOS, watchOS, and tvOS development. This beginner-friendly tutorial takes you from zero to building your first iOS app.

Why Swift?

  • Modern syntax - Clean, readable, and concise
  • Safe by design - Eliminates common programming errors
  • Fast performance - Compiled to optimized native code
  • Apple's future - Primary language for all Apple platforms
  • Strong community - Large ecosystem of libraries and resources

Prerequisites

Before starting, you'll need:

  • Mac computer (required for iOS development)
  • Xcode (free from App Store)
  • Apple ID (free)
  • Basic programming concepts understanding

Setting Up Your Environment

1. Install Xcode

# From App Store (recommended)
# Or via command line:
xcode-select --install

2. Create Your First Project

  1. Open Xcode
  2. Choose "Create New Project"
  3. Select "iOS" → "App"
  4. Configure:
    • Product Name: MyFirstApp
    • Interface: SwiftUI (or Storyboard for UIKit)
    • Language: Swift

Swift Basics

Variables and Constants

// Variables (can be changed)
var name = "John"
var age = 25
var isStudent = true

// Constants (cannot be changed)
let pi = 3.14159
let appName = "My App"

// Explicit types
var score: Int = 100
var price: Double = 29.99
var greeting: String = "Hello"
var isActive: Bool = false

// Type inference
var city = "New York" // Swift infers String
var count = 42 // Swift infers Int

Basic Types

// Integers
let integer: Int = 42
let unsignedInt: UInt = 42

// Floating-point
let float: Float = 3.14
let double: Double = 3.14159265359

// Strings
let message = "Hello, World!"
let multiline = """
    This is a
    multiline string
    """

// String interpolation
let name = "Alice"
let greeting = "Hello, \(name)!"

// Booleans
let isEnabled = true
let isHidden = false

Optionals

// Optional - can hold a value OR be nil
var middleName: String? = nil
var age: Int? = 25

// Unwrapping optionals
if let name = middleName {
    print("Middle name is \(name)")
} else {
    print("No middle name")
}

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

// Nil coalescing
let displayName = middleName ?? "Unknown"

// Optional chaining
let length = middleName?.count

// Force unwrapping (use carefully!)
let forcedName = middleName! // Crashes if nil

Collections

// Arrays
var fruits = ["Apple", "Banana", "Orange"]
fruits.append("Grape")
fruits.remove(at: 1)
let first = fruits[0]
let count = fruits.count

// Sets (unique values)
var colors: Set = ["Red", "Green", "Blue"]
colors.insert("Yellow")
colors.contains("Red") // true

// Dictionaries
var scores: [String: Int] = [
    "Alice": 95,
    "Bob": 87,
    "Charlie": 92
]
scores["David"] = 88
let aliceScore = scores["Alice"] // Optional Int

// Iterating
for fruit in fruits {
    print(fruit)
}

for (name, score) in scores {
    print("\(name): \(score)")
}

for i in 0..<5 {
    print(i) // 0, 1, 2, 3, 4
}

Control Flow

// If-else
let temperature = 75

if temperature > 80 {
    print("It's hot!")
} else if temperature > 60 {
    print("It's nice")
} else {
    print("It's cold")
}

// Switch
let grade = "A"

switch grade {
case "A":
    print("Excellent!")
case "B":
    print("Good")
case "C":
    print("Average")
default:
    print("Needs improvement")
}

// Switch with ranges
let score = 85

switch score {
case 90...100:
    print("A")
case 80..<90:
    print("B")
case 70..<80:
    print("C")
default:
    print("F")
}

// While loops
var count = 0
while count < 5 {
    print(count)
    count += 1
}

// Repeat-while (do-while)
repeat {
    print(count)
    count -= 1
} while count > 0

Functions

// Basic function
func sayHello() {
    print("Hello!")
}

// Function with parameters
func greet(name: String) {
    print("Hello, \(name)!")
}

// Function with return value
func add(a: Int, b: Int) -> Int {
    return a + b
}

// Multiple return values (tuples)
func getMinMax(numbers: [Int]) -> (min: Int, max: Int)? {
    guard let min = numbers.min(),
          let max = numbers.max() else {
        return nil
    }
    return (min, max)
}

// External and internal parameter names
func greet(person name: String, from hometown: String) {
    print("Hello \(name) from \(hometown)!")
}
greet(person: "Alice", from: "Boston")

// Default parameters
func greet(name: String = "World") {
    print("Hello, \(name)!")
}
greet() // "Hello, World!"
greet(name: "Alice") // "Hello, Alice!"

// Variadic parameters
func sum(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
}
sum(1, 2, 3, 4, 5) // 15

Closures

// Basic closure
let greet = { (name: String) -> String in
    return "Hello, \(name)!"
}
print(greet("Alice"))

// Closure as function parameter
func performOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let result = performOperation(10, 5) { $0 + $1 }
print(result) // 15

// Trailing closure syntax
let numbers = [1, 2, 3, 4, 5]

let doubled = numbers.map { $0 * 2 }
let evens = numbers.filter { $0 % 2 == 0 }
let sum = numbers.reduce(0) { $0 + $1 }

// Sorted with closure
let names = ["Charlie", "Alice", "Bob"]
let sorted = names.sorted { $0 < $1 }

Classes and Structures

// Structure (value type)
struct Person {
    var name: String
    var age: Int

    // Computed property
    var isAdult: Bool {
        return age >= 18
    }

    // Method
    func greet() {
        print("Hello, I'm \(name)")
    }

    // Mutating method (for structs)
    mutating func haveBirthday() {
        age += 1
    }
}

var person = Person(name: "Alice", age: 25)
person.greet()
person.haveBirthday()

// Class (reference type)
class Vehicle {
    var brand: String
    var speed: Int = 0

    init(brand: String) {
        self.brand = brand
    }

    func accelerate() {
        speed += 10
    }

    func describe() -> String {
        return "\(brand) moving at \(speed) mph"
    }
}

// Inheritance
class Car: Vehicle {
    var numberOfDoors: Int

    init(brand: String, doors: Int) {
        self.numberOfDoors = doors
        super.init(brand: brand)
    }

    override func describe() -> String {
        return "\(brand) with \(numberOfDoors) doors at \(speed) mph"
    }
}

let car = Car(brand: "Toyota", doors: 4)
car.accelerate()
print(car.describe())

Protocols

// Define a protocol
protocol Drawable {
    func draw()
}

protocol Describable {
    var description: String { get }
}

// Implement protocols
struct Circle: Drawable, Describable {
    var radius: Double

    var description: String {
        return "Circle with radius \(radius)"
    }

    func draw() {
        print("Drawing a circle")
    }
}

// Protocol extensions
extension Drawable {
    func erase() {
        print("Erasing...")
    }
}

// Protocol as type
func render(item: Drawable) {
    item.draw()
}

Error Handling

// Define errors
enum NetworkError: Error {
    case noConnection
    case timeout
    case invalidResponse
    case serverError(code: Int)
}

// Throwing function
func fetchData(from url: String) throws -> Data {
    guard !url.isEmpty else {
        throw NetworkError.invalidResponse
    }

    // Simulate network call
    guard let data = Data(base64Encoded: "") else {
        throw NetworkError.noConnection
    }

    return data
}

// Handle errors
do {
    let data = try fetchData(from: "https://api.example.com")
    print("Got data: \(data)")
} catch NetworkError.noConnection {
    print("No internet connection")
} catch NetworkError.timeout {
    print("Request timed out")
} catch {
    print("Unknown error: \(error)")
}

// try? returns optional
let data = try? fetchData(from: "https://api.example.com")

SwiftUI Basics

Your First SwiftUI View

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello, SwiftUI!")
                .font(.largeTitle)
                .fontWeight(.bold)
                .foregroundColor(.blue)

            Image(systemName: "star.fill")
                .font(.system(size: 50))
                .foregroundColor(.yellow)

            Button("Tap Me") {
                print("Button tapped!")
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

State and Binding

import SwiftUI

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 20) {
            Text("Count: \(count)")
                .font(.title)

            HStack {
                Button("Decrease") {
                    count -= 1
                }

                Button("Increase") {
                    count += 1
                }
            }
        }
    }
}

// Passing state to child views
struct ParentView: View {
    @State private var name = ""

    var body: some View {
        VStack {
            TextField("Enter name", text: $name)
                .textFieldStyle(.roundedBorder)

            ChildView(name: $name)
        }
        .padding()
    }
}

struct ChildView: View {
    @Binding var name: String

    var body: some View {
        Text("Hello, \(name.isEmpty ? "World" : name)!")
    }
}

Lists and Navigation

import SwiftUI

struct Task: Identifiable {
    let id = UUID()
    var title: String
    var isCompleted: Bool
}

struct TaskListView: View {
    @State private var tasks = [
        Task(title: "Learn Swift", isCompleted: true),
        Task(title: "Build an app", isCompleted: false),
        Task(title: "Ship to App Store", isCompleted: false)
    ]

    var body: some View {
        NavigationStack {
            List {
                ForEach(tasks) { task in
                    NavigationLink(destination: TaskDetailView(task: task)) {
                        HStack {
                            Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(task.isCompleted ? .green : .gray)
                            Text(task.title)
                        }
                    }
                }
                .onDelete(perform: deleteTask)
            }
            .navigationTitle("My Tasks")
            .toolbar {
                Button(action: addTask) {
                    Image(systemName: "plus")
                }
            }
        }
    }

    func addTask() {
        tasks.append(Task(title: "New Task", isCompleted: false))
    }

    func deleteTask(at offsets: IndexSet) {
        tasks.remove(atOffsets: offsets)
    }
}

struct TaskDetailView: View {
    let task: Task

    var body: some View {
        VStack {
            Text(task.title)
                .font(.title)
            Text(task.isCompleted ? "Completed" : "Not completed")
        }
        .navigationTitle("Task Details")
    }
}

Building a Complete App: Todo List

Model

// Models/TodoItem.swift
import Foundation

struct TodoItem: Identifiable, Codable {
    var id = UUID()
    var title: String
    var isCompleted: Bool = false
    var createdAt = Date()
}

ViewModel

// ViewModels/TodoViewModel.swift
import Foundation

class TodoViewModel: ObservableObject {
    @Published var todos: [TodoItem] = []

    private let saveKey = "todos"

    init() {
        loadTodos()
    }

    func addTodo(title: String) {
        let todo = TodoItem(title: title)
        todos.append(todo)
        saveTodos()
    }

    func toggleComplete(_ todo: TodoItem) {
        if let index = todos.firstIndex(where: { $0.id == todo.id }) {
            todos[index].isCompleted.toggle()
            saveTodos()
        }
    }

    func deleteTodo(_ todo: TodoItem) {
        todos.removeAll { $0.id == todo.id }
        saveTodos()
    }

    private func saveTodos() {
        if let encoded = try? JSONEncoder().encode(todos) {
            UserDefaults.standard.set(encoded, forKey: saveKey)
        }
    }

    private func loadTodos() {
        if let data = UserDefaults.standard.data(forKey: saveKey),
           let decoded = try? JSONDecoder().decode([TodoItem].self, from: data) {
            todos = decoded
        }
    }
}

Views

// Views/ContentView.swift
import SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = TodoViewModel()
    @State private var newTodoTitle = ""
    @State private var showingAddSheet = false

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.todos) { todo in
                    TodoRowView(todo: todo) {
                        viewModel.toggleComplete(todo)
                    }
                }
                .onDelete { indexSet in
                    indexSet.forEach { index in
                        viewModel.deleteTodo(viewModel.todos[index])
                    }
                }
            }
            .navigationTitle("My Todos")
            .toolbar {
                Button(action: { showingAddSheet = true }) {
                    Image(systemName: "plus")
                }
            }
            .sheet(isPresented: $showingAddSheet) {
                AddTodoView(viewModel: viewModel)
            }
            .overlay {
                if viewModel.todos.isEmpty {
                    ContentUnavailableView(
                        "No Todos",
                        systemImage: "checklist",
                        description: Text("Add your first todo!")
                    )
                }
            }
        }
    }
}

struct TodoRowView: View {
    let todo: TodoItem
    let onToggle: () -> Void

    var body: some View {
        HStack {
            Button(action: onToggle) {
                Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
                    .foregroundColor(todo.isCompleted ? .green : .gray)
                    .font(.title2)
            }
            .buttonStyle(.plain)

            Text(todo.title)
                .strikethrough(todo.isCompleted)
                .foregroundColor(todo.isCompleted ? .gray : .primary)

            Spacer()
        }
        .padding(.vertical, 4)
    }
}

struct AddTodoView: View {
    @ObservedObject var viewModel: TodoViewModel
    @Environment(\.dismiss) var dismiss
    @State private var title = ""

    var body: some View {
        NavigationStack {
            Form {
                TextField("Todo title", text: $title)
            }
            .navigationTitle("Add Todo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Add") {
                        viewModel.addTodo(title: title)
                        dismiss()
                    }
                    .disabled(title.isEmpty)
                }
            }
        }
    }
}

Networking with URLSession

// Services/APIService.swift
import Foundation

struct Post: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
}

class APIService {
    static let shared = APIService()

    func fetchPosts() async throws -> [Post] {
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!

        let (data, response) = try await URLSession.shared.data(from: url)

        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }

        return try JSONDecoder().decode([Post].self, from: data)
    }

    func createPost(title: String, body: String) async throws -> Post {
        let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let postData = ["title": title, "body": body, "userId": 1] as [String : Any]
        request.httpBody = try JSONSerialization.data(withJSONObject: postData)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(Post.self, from: data)
    }
}

// Using in SwiftUI
struct PostsView: View {
    @State private var posts: [Post] = []
    @State private var isLoading = false

    var body: some View {
        List(posts) { post in
            VStack(alignment: .leading) {
                Text(post.title)
                    .font(.headline)
                Text(post.body)
                    .font(.subheadline)
                    .foregroundColor(.gray)
            }
        }
        .task {
            await loadPosts()
        }
    }

    func loadPosts() async {
        isLoading = true
        do {
            posts = try await APIService.shared.fetchPosts()
        } catch {
            print("Error: \(error)")
        }
        isLoading = false
    }
}

Next Steps

  1. Master SwiftUI - Apple's declarative UI framework
  2. Learn Combine - Reactive programming for iOS
  3. Core Data - Local data persistence
  4. Firebase - Backend services
  5. App Store submission - Publishing your app
  6. Testing - Unit and UI testing

Conclusion

You've learned Swift basics and built a functional iOS app! Swift's modern syntax and safety features make it excellent for iOS development.

Need help with your iOS project? Contact Hevcode for professional iOS development services. We build native Swift applications for iPhone and iPad.

Related Articles

Tags:SwiftiOS DevelopmentTutorialSwiftUIMobile Development

Need help with your project?

We've helped 534+ clients build successful apps. Let's discuss yours.

Ready to Build Your App?

534+ projects delivered • 4.9★ rating • 6+ years experience

Let's discuss your project — no obligations, just a straightforward conversation.