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
- Apple Developer Documentation - Official iOS docs
- Hacking with Swift - SwiftUI - SwiftUI tutorials
- Hacking with Swift - UIKit - UIKit examples
- Human Interface Guidelines - iOS design guidelines
- Swift Language Cheatsheet - Swift syntax reference
- Apple's Swift Programming Guide - Official Swift guide
- WWDC Videos - Apple conference sessions