NexusCS

iOS Development

Mobile
Quick reference for iOS development with SwiftUI and UIKit frameworks. Covers state management, navigation, networking, and iOS-specific APIs.
ios
swiftui
uikit
apple
mobile

Getting Started

SwiftUI App Structure

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

iOS 14+ app entry point.

UIKit App Structure

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
        return true
    }
}

Traditional UIKit app delegate.

SwiftUI + UIKit Bridge

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
    ) -> Bool {
        // UIKit setup code
        return true
    }
}

Use UIKit delegate in SwiftUI app.

SwiftUI State Management

@State

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

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
    }
}

Local mutable state owned by view.

@Binding

struct ParentView: View {
    @State private var isOn = false

    var body: some View {
        ToggleView(isOn: $isOn)
    }
}

struct ToggleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

Two-way binding to parent's state.

@StateObject

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    func fetch() {
        // Load data
    }
}

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        List(viewModel.items, id: \.self) { item in
            Text(item)
        }
        .onAppear { viewModel.fetch() }
    }
}

View owns observable object lifecycle.

@ObservedObject

struct DetailView: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Text(viewModel.title)
    }
}

View observes external object (doesn't own).

@EnvironmentObject

class AppSettings: ObservableObject {
    @Published var isDarkMode = false
}

@main
struct MyApp: App {
    @StateObject private var settings = AppSettings()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(settings)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var settings: AppSettings

    var body: some View {
        Toggle("Dark Mode", isOn: $settings.isDarkMode)
    }
}

Dependency injection through environment.

@Published

class DataModel: ObservableObject {
    @Published var username = ""
    @Published var items: [Item] = []

    init() {
        $username
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .sink { newValue in
                print("Username changed: \(newValue)")
            }
    }
}

Property publishes changes to observers.

@AppStorage

struct SettingsView: View {
    @AppStorage("username") private var username = ""
    @AppStorage("fontSize") private var fontSize = 16.0

    var body: some View {
        Form {
            TextField("Username", text: $username)
            Slider(value: $fontSize, in: 12...24)
        }
    }
}

UserDefaults-backed property wrapper.

Observable Macro (iOS 17+)

import Observation

@Observable
class ViewModel {
    var title: String = ""
    var items: [String] = []
}

struct ContentView: View {
    @State private var viewModel = ViewModel()

    var body: some View {
        Text(viewModel.title)
    }
}

Simplified observable pattern (iOS 17+).

SwiftUI Views

Text

Text("Hello, World!")
    .font(.title)
    .foregroundStyle(.blue)
    .bold()

Text("Multi\nLine")
    .multilineTextAlignment(.center)

Text("**Bold** and *italic*")

Display text with styling.

Button

Button("Tap Me") {
    print("Tapped")
}

Button(action: { /* action */ }) {
    Label("Save", systemImage: "square.and.arrow.down")
}
.buttonStyle(.borderedProminent)

Tappable button with action.

Image

Image("photo")
    .resizable()
    .scaledToFit()

Image(systemName: "star.fill")
    .foregroundStyle(.yellow)
    .font(.largeTitle)

Display images from assets or SF Symbols.

List

List {
    Text("Row 1")
    Text("Row 2")
    Text("Row 3")
}

List(items, id: \.id) { item in
    Text(item.name)
}

List {
    ForEach(items) { item in
        ItemRow(item: item)
    }
    .onDelete(perform: deleteItems)
}

Scrollable list of views.

Form

Form {
    Section("Profile") {
        TextField("Name", text: $name)
        Toggle("Notifications", isOn: $notificationsOn)
    }

    Section("Settings") {
        Picker("Theme", selection: $theme) {
            Text("Light").tag(0)
            Text("Dark").tag(1)
        }
    }
}

Grouped input controls.

TextField & SecureField

TextField("Email", text: $email)
    .textContentType(.emailAddress)
    .keyboardType(.emailAddress)
    .autocapitalization(.none)

SecureField("Password", text: $password)
    .textContentType(.password)

Text input fields.

Picker

Picker("Color", selection: $selectedColor) {
    Text("Red").tag(Color.red)
    Text("Green").tag(Color.green)
    Text("Blue").tag(Color.blue)
}
.pickerStyle(.segmented)

Picker("Country", selection: $country) {
    ForEach(countries, id: \.self) { country in
        Text(country)
    }
}

Selection control with multiple styles.

SwiftUI Layout

VStack

VStack(alignment: .leading, spacing: 10) {
    Text("Title")
        .font(.headline)
    Text("Subtitle")
        .font(.subheadline)
}

Vertical stack of views.

HStack

HStack(spacing: 20) {
    Image(systemName: "star")
    Text("Featured")
    Spacer()
    Image(systemName: "chevron.right")
}

Horizontal stack of views.

ZStack

ZStack(alignment: .bottomTrailing) {
    Image("background")
        .resizable()

    Text("Overlay")
        .padding()
        .background(.ultraThinMaterial)
}

Overlapping views (z-axis).

Grid (iOS 16+)

Grid {
    GridRow {
        Text("A1")
        Text("B1")
    }
    GridRow {
        Text("A2")
        Text("B2")
    }
}

LazyVGrid(columns: [
    GridItem(.flexible()),
    GridItem(.flexible()),
    GridItem(.flexible())
]) {
    ForEach(items) { item in
        ItemView(item: item)
    }
}

Grid layouts with flexible columns.

Spacer & Divider

HStack {
    Text("Left")
    Spacer()
    Text("Right")
}

VStack {
    Text("Above")
    Divider()
    Text("Below")
}

Flexible space and visual separators.

Padding & Frame

Text("Padded")
    .padding()
    .padding(.horizontal, 20)

Text("Fixed Size")
    .frame(width: 200, height: 100)

Text("Min/Max")
    .frame(maxWidth: .infinity, alignment: .leading)

Control size and spacing.

Navigation (SwiftUI)

NavigationStack (iOS 16+)

NavigationStack {
    List(items) { item in
        NavigationLink(item.name, value: item)
    }
    .navigationDestination(for: Item.self) { item in
        DetailView(item: item)
    }
    .navigationTitle("Items")
}

Type-safe navigation stack.

NavigationLink

// Value-based (iOS 16+)
NavigationLink("Details", value: item)

// Destination-based (legacy)
NavigationLink {
    DetailView(item: item)
} label: {
    Text(item.name)
}

Link to navigation destination.

Sheet

struct ContentView: View {
    @State private var showSheet = false

    var body: some View {
        Button("Show Sheet") {
            showSheet = true
        }
        .sheet(isPresented: $showSheet) {
            SheetView()
        }
    }
}

Modal sheet presentation.

Full Screen Cover

Button("Show Full Screen") {
    showFullScreen = true
}
.fullScreenCover(isPresented: $showFullScreen) {
    FullScreenView()
}

Full-screen modal presentation.

Alert & Confirmation Dialog

.alert("Delete Item?", isPresented: $showAlert) {
    Button("Delete", role: .destructive) {
        deleteItem()
    }
    Button("Cancel", role: .cancel) { }
}

.confirmationDialog("Choose Option", isPresented: $showDialog) {
    Button("Option 1") { }
    Button("Option 2") { }
    Button("Cancel", role: .cancel) { }
}

Alerts and action sheets.

TabView

TabView {
    HomeView()
        .tabItem {
            Label("Home", systemImage: "house")
        }

    SettingsView()
        .tabItem {
            Label("Settings", systemImage: "gear")
        }
}

Tab bar navigation.

UIKit Basics

UIViewController Lifecycle

class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Setup after view loads
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // Before view appears
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // After view appears
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // Before view disappears
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // After view disappears
    }
}

View controller lifecycle methods.

UILabel

let label = UILabel()
label.text = "Hello, UIKit"
label.font = .systemFont(ofSize: 20, weight: .bold)
label.textColor = .systemBlue
label.textAlignment = .center
label.numberOfLines = 0
view.addSubview(label)

Display text in UIKit.

UIButton

let button = UIButton(type: .system)
button.setTitle("Tap Me", for: .normal)
button.addTarget(
    self,
    action: #selector(buttonTapped),
    for: .touchUpInside
)

@objc func buttonTapped() {
    print("Button tapped")
}

Tappable button with target-action.

UIImageView

let imageView = UIImageView()
imageView.image = UIImage(named: "photo")
imageView.contentMode = .scaleAspectFit

// SF Symbol
imageView.image = UIImage(systemName: "star.fill")
imageView.tintColor = .systemYellow

Display images in UIKit.

UITableView

class TableViewController: UIViewController, UITableViewDataSource {
    let tableView = UITableView()
    let items = ["Item 1", "Item 2", "Item 3"]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    }

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

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

Scrollable table view with data source.

UITextField

let textField = UITextField()
textField.placeholder = "Enter text"
textField.borderStyle = .roundedRect
textField.keyboardType = .emailAddress
textField.autocapitalizationType = .none
textField.delegate = self

extension ViewController: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

Text input in UIKit.

Auto Layout (UIKit)

NSLayoutConstraint

let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)

NSLayoutConstraint.activate([
    label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    label.widthAnchor.constraint(equalToConstant: 200),
    label.heightAnchor.constraint(equalToConstant: 40)
])

Programmatic Auto Layout constraints.

Layout Anchors

button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)

NSLayoutConstraint.activate([
    button.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
    button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
    button.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
    button.heightAnchor.constraint(equalToConstant: 50)
])

Anchor-based constraints with margins.

UIStackView

let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 10
stackView.alignment = .fill
stackView.distribution = .fillEqually

stackView.addArrangedSubview(label)
stackView.addArrangedSubview(button)
stackView.addArrangedSubview(textField)

stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)

NSLayoutConstraint.activate([
    stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
    stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
    stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])

Stack views for layout.

Navigation (UIKit)

UINavigationController

// In AppDelegate or SceneDelegate
let rootVC = HomeViewController()
let navController = UINavigationController(rootViewController: rootVC)
window?.rootViewController = navController

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

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

Navigation stack in UIKit.

Modal Presentation

let modalVC = ModalViewController()
modalVC.modalPresentationStyle = .pageSheet
present(modalVC, animated: true)

// Dismiss
dismiss(animated: true)

Modal presentation styles.

Passing Data

class DetailViewController: UIViewController {
    var item: Item?

    override func viewDidLoad() {
        super.viewDidLoad()
        title = item?.name
    }
}

// Presenting code
let detailVC = DetailViewController()
detailVC.item = selectedItem
navigationController?.pushViewController(detailVC, animated: true)

Pass data to view controllers.

Data Persistence

UserDefaults

// Save
UserDefaults.standard.set("John", forKey: "username")
UserDefaults.standard.set(25, forKey: "age")
UserDefaults.standard.set(true, forKey: "isDarkMode")

// Load
let username = UserDefaults.standard.string(forKey: "username")
let age = UserDefaults.standard.integer(forKey: "age")
let isDarkMode = UserDefaults.standard.bool(forKey: "isDarkMode")

// Remove
UserDefaults.standard.removeObject(forKey: "username")

Simple key-value persistence.

FileManager

// Get documents directory
let documentsURL = FileManager.default.urls(
    for: .documentDirectory,
    in: .userDomainMask
).first!

// Save data
let fileURL = documentsURL.appendingPathComponent("data.json")
try? data.write(to: fileURL)

// Load data
if let loadedData = try? Data(contentsOf: fileURL) {
    // Use data
}

// Check if file exists
if FileManager.default.fileExists(atPath: fileURL.path) {
    // File exists
}

File system operations.

Codable with FileManager

struct User: Codable {
    let name: String
    let age: Int
}

// Save
let user = User(name: "John", age: 25)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(user) {
    let url = documentsURL.appendingPathComponent("user.json")
    try? encoded.write(to: url)
}

// Load
if let data = try? Data(contentsOf: url) {
    let decoder = JSONDecoder()
    if let user = try? decoder.decode(User.self, from: data) {
        print(user.name)
    }
}

JSON encoding/decoding with files.

Core Data Basics

import CoreData

// Fetch
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
let items = try? viewContext.fetch(fetchRequest)

// Create
let newItem = Item(context: viewContext)
newItem.name = "New Item"
newItem.timestamp = Date()

// Save
try? viewContext.save()

// Delete
viewContext.delete(item)
try? viewContext.save()

Core Data operations.

@FetchRequest (SwiftUI)

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default
    )
    private var items: FetchedResults<Item>

    var body: some View {
        List {
            ForEach(items) { item in
                Text(item.name ?? "")
            }
            .onDelete(perform: deleteItems)
        }
    }

    func deleteItems(offsets: IndexSet) {
        offsets.map { items[$0] }.forEach(viewContext.delete)
        try? viewContext.save()
    }
}

Core Data in SwiftUI.

Networking

URLSession with async/await

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

func fetchPosts() async throws -> [Post] {
    let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
    let (data, _) = try await URLSession.shared.data(from: url)
    let posts = try JSONDecoder().decode([Post].self, from: data)
    return posts
}

// Usage
Task {
    do {
        let posts = try await fetchPosts()
        print(posts.count)
    } catch {
        print("Error: \(error)")
    }
}

Modern async networking.

POST Request

func createPost(title: String, body: String) async throws -> Post {
    var request = URLRequest(url: URL(string: "https://api.example.com/posts")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")

    let postData = ["title": title, "body": body]
    request.httpBody = try JSONEncoder().encode(postData)

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

POST request with Codable.

Download Image

func loadImage(from urlString: String) async throws -> UIImage? {
    guard let url = URL(string: urlString) else { return nil }
    let (data, _) = try await URLSession.shared.data(from: url)
    return UIImage(data: data)
}

// SwiftUI usage
struct AsyncImageView: View {
    let url: String
    @State private var image: UIImage?

    var body: some View {
        Group {
            if let image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
            } else {
                ProgressView()
            }
        }
        .task {
            image = try? await loadImage(from: url)
        }
    }
}

Async image loading.

AsyncImage (SwiftUI)

AsyncImage(url: URL(string: "https://example.com/image.jpg")) { image in
    image
        .resizable()
        .scaledToFit()
} placeholder: {
    ProgressView()
}
.frame(width: 200, height: 200)

// With phase
AsyncImage(url: URL(string: imageURL)) { phase in
    switch phase {
    case .empty:
        ProgressView()
    case .success(let image):
        image.resizable().scaledToFit()
    case .failure:
        Image(systemName: "photo")
    @unknown default:
        EmptyView()
    }
}

Built-in async image loading.

Error Handling

enum NetworkError: Error {
    case invalidURL
    case requestFailed
    case invalidResponse
    case decodingFailed
}

func fetchData<T: Codable>(from urlString: String) async throws -> T {
    guard let url = URL(string: urlString) else {
        throw NetworkError.invalidURL
    }

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

    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw NetworkError.invalidResponse
    }

    do {
        return try JSONDecoder().decode(T.self, from: data)
    } catch {
        throw NetworkError.decodingFailed
    }
}

Custom error handling.

iOS Patterns

Delegation Pattern

protocol DataDelegate: AnyObject {
    func didReceiveData(_ data: String)
}

class DataProvider {
    weak var delegate: DataDelegate?

    func fetchData() {
        // Fetch data
        delegate?.didReceiveData("Result")
    }
}

class ViewController: UIViewController, DataDelegate {
    let provider = DataProvider()

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

    func didReceiveData(_ data: String) {
        print("Received: \(data)")
    }
}

Delegate pattern with weak reference.

NotificationCenter

// Post notification
NotificationCenter.default.post(
    name: Notification.Name("DataUpdated"),
    object: nil,
    userInfo: ["key": "value"]
)

// Observe notification
NotificationCenter.default.addObserver(
    self,
    selector: #selector(handleNotification),
    name: Notification.Name("DataUpdated"),
    object: nil
)

@objc func handleNotification(_ notification: Notification) {
    if let value = notification.userInfo?["key"] as? String {
        print(value)
    }
}

// Remove observer
deinit {
    NotificationCenter.default.removeObserver(self)
}

Publish-subscribe pattern.

NotificationCenter (SwiftUI)

struct ContentView: View {
    var body: some View {
        Text("Listening")
            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("DataUpdated"))) { notification in
                if let value = notification.userInfo?["key"] as? String {
                    print(value)
                }
            }
    }
}

Notifications in SwiftUI.

MVVM Pattern

// Model
struct User: Codable {
    let id: Int
    let name: String
}

// ViewModel
@MainActor
class UserViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false
    @Published var errorMessage: String?

    func fetchUsers() async {
        isLoading = true
        defer { isLoading = false }

        do {
            let url = URL(string: "https://api.example.com/users")!
            let (data, _) = try await URLSession.shared.data(from: url)
            users = try JSONDecoder().decode([User].self, from: data)
        } catch {
            errorMessage = error.localizedDescription
        }
    }
}

// View
struct UserListView: View {
    @StateObject private var viewModel = UserViewModel()

    var body: some View {
        List(viewModel.users, id: \.id) { user in
            Text(user.name)
        }
        .task {
            await viewModel.fetchUsers()
        }
        .overlay {
            if viewModel.isLoading {
                ProgressView()
            }
        }
    }
}

MVVM architecture in SwiftUI.

Combine Publishers

import Combine

class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [String] = []

    private var cancellables = Set<AnyCancellable>()

    init() {
        $searchText
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }

    func performSearch(_ text: String) {
        // Search logic
    }
}

Combine framework basics.

Location Services

CoreLocation Setup

import CoreLocation

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    @Published var location: CLLocation?
    @Published var authorizationStatus: CLAuthorizationStatus?

    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
    }

    func requestPermission() {
        manager.requestWhenInUseAuthorization()
    }

    func startUpdating() {
        manager.startUpdatingLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        location = locations.first
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        authorizationStatus = manager.authorizationStatus
    }
}

Location manager with permissions.

Info.plist Keys

<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show nearby places</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location for background tracking</string>

Required privacy descriptions.

Using Location (SwiftUI)

struct LocationView: View {
    @StateObject private var locationManager = LocationManager()

    var body: some View {
        VStack {
            if let location = locationManager.location {
                Text("Lat: \(location.coordinate.latitude)")
                Text("Lon: \(location.coordinate.longitude)")
            } else {
                Button("Request Location") {
                    locationManager.requestPermission()
                    locationManager.startUpdating()
                }
            }
        }
    }
}

Location in SwiftUI.

Permissions

Camera Permission

import AVFoundation

func requestCameraPermission() {
    AVCaptureDevice.requestAccess(for: .video) { granted in
        if granted {
            print("Camera access granted")
        } else {
            print("Camera access denied")
        }
    }
}

// Check status
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
    print("Authorized")
case .denied, .restricted:
    print("Denied")
case .notDetermined:
    requestCameraPermission()
@unknown default:
    break
}

Camera access permission.

Photo Library Permission

import Photos

func requestPhotoLibraryPermission() {
    PHPhotoLibrary.requestAuthorization { status in
        switch status {
        case .authorized:
            print("Authorized")
        case .denied, .restricted:
            print("Denied")
        case .notDetermined:
            print("Not determined")
        case .limited:
            print("Limited access")
        @unknown default:
            break
        }
    }
}

Photo library access.

Info.plist Privacy Keys

<key>NSCameraUsageDescription</key>
<string>We need camera access to take photos</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>We need photo library access to select images</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need permission to save photos</string>

<key>NSMicrophoneUsageDescription</key>
<string>We need microphone access to record audio</string>

<key>NSContactsUsageDescription</key>
<string>We need contacts access to find friends</string>

Common privacy descriptions.

Push Notifications

Request Permission

import UserNotifications

func requestNotificationPermission() {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
        if granted {
            DispatchQueue.main.async {
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
}

// In AppDelegate
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print("Device token: \(token)")
}

Remote notification setup.

Local Notification

func scheduleLocalNotification() {
    let content = UNMutableNotificationContent()
    content.title = "Reminder"
    content.body = "Don't forget to check the app!"
    content.sound = .default

    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: false)
    let request = UNNotificationRequest(identifier: "reminder", content: content, trigger: trigger)

    UNUserNotificationCenter.current().add(request) { error in
        if let error {
            print("Error: \(error)")
        }
    }
}

Schedule local notification.

Handle Notifications

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Notification received while app is in foreground
        completionHandler([.banner, .sound])
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        // User tapped notification
        let userInfo = response.notification.request.content.userInfo
        print("Notification tapped: \(userInfo)")
        completionHandler()
    }
}

Notification delegate methods.

Common Modifiers (SwiftUI)

Styling

Text("Hello")
    .font(.title)
    .foregroundStyle(.blue)
    .bold()
    .italic()
    .underline()
    .strikethrough()

Text styling modifiers.

Layout

View()
    .frame(width: 100, height: 100)
    .padding()
    .padding(.horizontal, 20)
    .background(.blue)
    .cornerRadius(10)
    .shadow(radius: 5)
    .offset(x: 10, y: 20)

Layout and visual modifiers.

Interaction

View()
    .onTapGesture {
        print("Tapped")
    }
    .onLongPressGesture {
        print("Long pressed")
    }
    .gesture(DragGesture())
    .disabled(isDisabled)
    .opacity(isVisible ? 1 : 0.5)

Gesture and interaction modifiers.

Lifecycle

View()
    .onAppear {
        print("View appeared")
    }
    .onDisappear {
        print("View disappeared")
    }
    .task {
        await loadData()
    }
    .onChange(of: value) { oldValue, newValue in
        print("Value changed")
    }

View lifecycle modifiers.

Animations

Text("Animate")
    .scaleEffect(isLarge ? 1.5 : 1.0)
    .animation(.easeInOut, value: isLarge)

Text("Spring")
    .offset(y: offset)
    .animation(.spring(response: 0.5, dampingFraction: 0.6), value: offset)

withAnimation(.easeInOut(duration: 0.3)) {
    isVisible.toggle()
}

Animation modifiers.

Gotchas

Params as Promises (Next.js 16)

// iOS doesn't have this - but beware of iOS 17+ API availability
if #available(iOS 17, *) {
    // Use iOS 17+ features
} else {
    // Fallback for older iOS
}

Always check API availability.

@StateObject vs @ObservedObject

Use @StateObject when the view owns the object lifecycle. Use @ObservedObject when the object is passed from a parent.

Memory Leaks with Closures

// BAD - strong reference cycle
class ViewModel {
    var onUpdate: (() -> Void)?

    func setup() {
        onUpdate = {
            self.doSomething() // Captures self strongly
        }
    }
}

// GOOD - weak self
onUpdate = { [weak self] in
    self?.doSomething()
}

Always use [weak self] in closures.

Main Thread UI Updates

// UIKit
DispatchQueue.main.async {
    label.text = "Updated"
}

// SwiftUI with @MainActor
@MainActor
class ViewModel: ObservableObject {
    @Published var data: String = ""
}

UI updates must be on main thread.

Force Unwrapping

// BAD
let name = user.name!

// GOOD
if let name = user.name {
    print(name)
}

guard let name = user.name else { return }

Avoid force unwrapping with !.

Also See