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
- Open Xcode
- Choose "Create New Project"
- Select "iOS" → "App"
- 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
- Master SwiftUI - Apple's declarative UI framework
- Learn Combine - Reactive programming for iOS
- Core Data - Local data persistence
- Firebase - Backend services
- App Store submission - Publishing your app
- 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.