[{"content":"This is my running log of Git moves I reach for when I do work. Each mini-how-to includes the exact commands plus a quick note on what to watch for.\nSquash Multiple Commits Into One Use interactive rebase to collapse the last n commits into a single, tidy changeset.\nOptional safety: create a backup branch before rebasing:\ngit branch backup/feature-branch-before-squash git rebase -i HEAD~3 Replace 3 with however many commits you want to include, counting the current HEAD as part of that total. HEAD refers to the current commit, HEAD~1 to its parent, HEAD~2 to the one before that, and so on. When you run git rebase -i HEAD~3, Git lists the last three commits including HEAD. Run git log --oneline to count backward until you reach the oldest commit you intend to squash.\nTip: Use HEAD~3^ if you need to include the fourth commit in the interactive list.\nFor example, if your history looks like this (newest first):\na1b2c3 (HEAD) d4e5f6 g7h8i9 j0k1l2 The command git rebase -i HEAD~3 opens the editor with:\npick j0k1l2 pick g7h8i9 pick d4e5f6 In the editor:\nLeave the first commit as pick Change the rest to squash (or s) Save, then compose the final commit message If conflicts appear, resolve them and resume with git rebase --continue.\nTo abort the squash at any point, run:\ngit rebase --abort Force Push After Squashing Once history changes, update the remote branch.\ngit push --force-with-lease origin feature/my-branch --force-with-lease protects teammates by refusing to overwrite work you haven’t fetched.\nCreate a Local Repo and Wire Up a Remote mkdir my-project \u0026amp;\u0026amp; cd my-project git init # add files, then commit git add . git commit -m \u0026#34;Initial commit\u0026#34; git remote add origin git@github.com:username/my-project.git git push -u origin main If the remote is empty, main (or master) becomes the default branch once you push with -u. Double-check the URL format (git@ for SSH, https:// for HTTPS) before adding the remote.\nTrack a Remote Branch Locally When you create a local branch that should follow an existing remote branch, set the upstream explicitly.\ngit switch -c feature/api-integration origin/feature/api-integration # or, if branch already exists locally git branch --set-upstream-to=origin/feature/api-integration Now git pull and git push know which remote branch to use. If Git complains that the remote ref is missing, fetch it first (git fetch origin feature/api-integration) and rerun the command.\nCreate a Branch and Push It Upstream Shortcut to create a branch, commit, and publish it with upstream tracking in one go.\ngit switch -c feature/search-bar git commit -am \u0026#34;Add search bar styles\u0026#34; git push -u origin feature/search-bar -u (or --set-upstream) wires the remote for future pushes. Remember that git commit -am only stages files Git already tracks—run git add first if you created new files.\nSync With the Latest From Main Without Merge Commits Rebase your current branch onto main to replay your commits on top of the latest state.\nOptional safety: create a backup before rebasing:\ngit branch backup/feature-search-bar-before-rebase Make sure you have no pending edits: git status -sb should report a clean tree. Update main so you are rebasing onto the real latest code. git switch main git pull --ff-only origin main Return to your feature branch (replace with your branch name). git switch feature/search-bar Replay your commits on top of the freshly updated main. git rebase main ⚠️ Rebase pointer:\nIn merges: ours = current branch, theirs = incoming.\nIn rebases: ours = target branch, theirs = replayed commit.\nPick the pieces you need, git add each resolved file, and continue with git rebase --continue.\nIf the rebase turns chaotic, bail out with git rebase --abort.\nOnce the history looks right, rerun your tests and publish the new linear history:\ngit push --force-with-lease origin feature/search-bar If you prefer to preserve merge commits instead, use git merge origin/main.\nRebase Onto the Remote Version of Your Branch When teammates update the remote feature branch, pull those commits in and resolve conflicts locally.\nOptional safety: create a backup before rebasing:\ngit branch backup/feature-payment-before-rebase Ensure a clean tree first: git status -sb. Update your knowledge of the remote branch. git fetch origin feature/payment Switch to your local branch that you want to replay. git switch feature/payment Rebase onto the remote tracking branch you fetched. git rebase origin/feature/payment ⚠️ Rebase pointer:\nIn merges: ours = current branch, theirs = incoming.\nIn rebases: ours = target branch, theirs = replayed commit.\nMerge the pieces you want, stage them with git add, and continue (git rebase --continue).\nIf the rebase turns chaotic, bail out with git rebase --abort.\nAlternatively, if your upstream is set, you can pull and rebase in one step:\ngit pull --rebase After the rebase, rerun tests and share the rewritten history safely:\ngit push --force-with-lease origin feature/payment Cherry-Pick a Fix Onto Another Branch Grab a single commit and apply it elsewhere.\ngit switch release/1.2 git cherry-pick \u0026lt;commit-sha\u0026gt; If conflicts arise, fix them, git add the files, and run git cherry-pick --continue.\nFor multiple commits, cherry-pick a range (the upper bound is exclusive):\ngit cherry-pick A..B Tip: Use A^..B if you want to include commit A itself.\nQuick Patch From a File Diff Generate a patch for one file and drop it into another branch or repo.\ngit diff main..feature/payment checkout.swift \u0026gt; checkout.patch git switch hotfix/payment-crash git apply checkout.patch Review the applied changes before committing.\ngit apply only affects the working tree; follow up with git add and git commit once you confirm the patch looks right.\nTip: Run git am checkout.patch instead if you want to preserve the original author and commit message from the patch.\nMerge Main Into Your Branch (Keep the Merge Commit) If your team relies on merge commits for context, integrate the latest main without rewriting history.\ngit fetch origin git merge --no-ff origin/main Resolve conflicts, run the test suite, and let Git create the merge commit message. --no-ff retains the merge node even when a fast-forward is possible, preserving the branch narrative.\nSee What Changed Between Two Branches Compare your feature branch to main, highlighting only the files or commits that differ.\ngit log --oneline main..feature/billing git diff --stat main...feature/billing Two dots (..) show unique commits; three dots (...) reveal files that diverged from the common ancestor.\nUndo the Last Commit (Keep Changes) Keep your working tree but remove the most recent commit.\ngit reset --soft HEAD~1 HEAD is the current commit, so HEAD~1 targets the one before it. The changes remain staged. To unstage but keep edits, use git reset --mixed HEAD~1.\nRecover Work You Thought Was Gone If a commit vanished after a reset or rebase, scan the reflog.\ngit reflog You can use git show \u0026lt;reflog-sha\u0026gt; to inspect the commit before checking it out.\ngit switch --detach \u0026lt;reflog-sha\u0026gt; Create a new branch from that SHA to rescue the work: git switch -c rescue/forgotten \u0026lt;sha\u0026gt;.\nStash Selective Files Avoid stashing the whole tree by selecting specific paths.\ngit stash push -- src/HomeView.swift git stash list git stash pop Combine with --keep-index to stash only unstaged changes. Stashes are stored in order; use git stash list to view entries and git stash pop stash@{1} to apply a specific one.\nClean Untracked Files Safely Preview deletions before scrubbing untracked files and directories.\ngit clean -nd git clean -fd -n performs a dry run; -f is required to delete, and -d includes directories. Double-check the dry-run output carefully—git clean is irreversible once it runs with -f.\nRevert a Commit That Already Hit Main Create a new commit that undoes a previous one (keeps history intact).\ngit revert \u0026lt;commit-sha\u0026gt; Grab the commit SHA from git log --oneline. If the revert itself fails because of conflicts, resolve them, git add the files, and run git revert --continue.\nTip: Need to revert several commits in one go? Use git revert -n \u0026lt;sha1\u0026gt; \u0026lt;sha2\u0026gt; ... to stage the reversions and commit once at the end.\nRename a Branch Locally and Remotely git branch -m old-name new-name git push origin --delete old-name git push -u origin new-name If the branch is protected, you may need admin permissions (or to lift protection temporarily) before deleting it remotely. Afterwards, clean up stale references locally:\ngit fetch --prune Anyone else using the old branch should do the same and check out the new name.\nI add to this page whenever I find myself repeating a Git flow. If you’re reading along and need another scenario covered, let me know and I’ll expand the list.\n","date":"2025-10-27T17:42:48Z","permalink":"/p/git-how-tos-i-reach-for-daily-work/","title":"Git How-Tos I Reach For Daily Work"},{"content":"Swift\u0026rsquo;s Codable APIs make JSON parsing approachable, but real-world payloads often include missing keys, dates, snake_case fields, or constrained enums that deserve stronger typing. This guide captures the patterns I reach for when building production decoders, so future-me has a single reference of best practices and battle-tested examples.\nSample JSON Payload { \u0026#34;id\u0026#34;: 42, \u0026#34;username\u0026#34;: \u0026#34;entangled_dev\u0026#34;, \u0026#34;display_name\u0026#34;: \u0026#34;Entangled Dev\u0026#34;, \u0026#34;created_at\u0026#34;: \u0026#34;2025-10-01T09:42:00Z\u0026#34;, \u0026#34;is_admin\u0026#34;: false, \u0026#34;profile\u0026#34;: { \u0026#34;bio\u0026#34;: \u0026#34;Building with Swift and SwiftUI.\u0026#34;, \u0026#34;website\u0026#34;: \u0026#34;https://entangled.dev\u0026#34; }, \u0026#34;favorite_languages\u0026#34;: [\u0026#34;Swift\u0026#34;, \u0026#34;Rust\u0026#34;, \u0026#34;TypeScript\u0026#34;] } Create a Codable Model struct User: Codable { let id: Int let username: String let displayName: String let createdAt: Date let isAdmin: Bool let profile: Profile let favoriteLanguages: [String] struct Profile: Codable { let bio: String let website: URL } enum CodingKeys: String, CodingKey { case id case username case displayName = \u0026#34;display_name\u0026#34; case createdAt = \u0026#34;created_at\u0026#34; case isAdmin = \u0026#34;is_admin\u0026#34; case profile case favoriteLanguages = \u0026#34;favorite_languages\u0026#34; } } CodingKeys let you align Swift\u0026rsquo;s camelCase with the snake_case keys from the backend, keeping the rest of the code clean.\nDecode with JSONDecoder Assume sampleJSONString is the raw JSON string you received from a network call or fixture.\nlet jsonData = sampleJSONString.data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 do { let user = try decoder.decode(User.self, from: jsonData) print(\u0026#34;Loaded user:\u0026#34;, user.username) } catch { print(\u0026#34;Failed to decode user:\u0026#34;, error.localizedDescription) } convertFromSnakeCase handles most key conversions automatically. Define explicit CodingKeys when the mapping is more nuanced or when you want control over a single property.\nConfigure a Shared Decoder For larger projects, configure JSONDecoder once so the rest of your codebase stays focused on domain logic.\nenum Decoders { static func api() -\u0026gt; JSONDecoder { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 decoder.dataDecodingStrategy = .base64 decoder.nonConformingFloatDecodingStrategy = .throw return decoder } } Returning a fresh decoder keeps the instance stateless and thread-safe. Keeping the configuration in one place makes it obvious which strategies are in play. If the API delivers milliseconds-since-epoch dates tomorrow, you only have one place to update.\nextension JSONDecoder.DateDecodingStrategy { static let millisecondsSince1970: JSONDecoder.DateDecodingStrategy = .custom { decoder in let container = try decoder.singleValueContainer() let milliseconds = try container.decode(Double.self) return Date(timeIntervalSince1970: milliseconds / 1000) } } Swap this strategy in whenever the service moves away from ISO 8601 timestamps.\nHandling Optional Data Use optionals for fields the API might omit and supply sensible defaults.\n{ \u0026#34;id\u0026#34;: \u0026#34;F2A0D5C2-3E43-4E58-880E-6BA16AD669E8\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Build SwiftUI Layouts Quickly\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 6 } struct Article: Codable { let id: UUID let title: String let subtitle: String? let readTimeMinutes: Int enum CodingKeys: String, CodingKey { case id case title case subtitle case readTimeMinutes = \u0026#34;read_time_minutes\u0026#34; } } let decoder = Decoders.api() let articleJSON = \u0026#34;\u0026#34;\u0026#34; { \u0026#34;id\u0026#34;: \u0026#34;F2A0D5C2-3E43-4E58-880E-6BA16AD669E8\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Build SwiftUI Layouts Quickly\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 6 } \u0026#34;\u0026#34;\u0026#34; let data = Data(articleJSON.utf8) let article = try decoder.decode(Article.self, from: data) print(article.subtitle ?? \u0026#34;No subtitle provided\u0026#34;) When a key is missing and the property is non-optional, decoding fails. Treat optional values as a contract with the API, annotating properties only when absence is valid.\nSupplying Defaults Without Optionals Sometimes the backend omits a value but you still want a non-optional property. Provide the default inside a custom initializer so callers always receive a fully-formed model.\n{ \u0026#34;enabled\u0026#34;: false } struct NotificationSettings: Codable { let isEnabled: Bool let frequency: Frequency enum CodingKeys: String, CodingKey { case isEnabled = \u0026#34;enabled\u0026#34; case frequency } enum Frequency: String, Codable { case immediate, hourly, daily } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) isEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? true frequency = try container.decodeIfPresent(Frequency.self, forKey: .frequency) ?? .daily } } let settingsJSON = \u0026#34;\u0026#34;\u0026#34; { \u0026#34;enabled\u0026#34;: false } \u0026#34;\u0026#34;\u0026#34; let settingsData = Data(settingsJSON.utf8) let settings = try Decoders.api().decode(NotificationSettings.self, from: settingsData) print(settings.frequency) // daily decoder.container(keyedBy:) hands you a keyed view into the JSON object so you can look up multiple fields by CodingKey. Think of it as opening a dictionary-like sub-decoder scoped to the current object. Without grabbing a container, you could only decode a single value at a time, which is fine for simple payloads but breaks down when you need to read several properties and coordinate defaults in one initializer.\nOnce you have a container, you reach into it with decode(_:forKey:) and decodeIfPresent(_:forKey:). They belong to the container—not JSONDecoder—and are the workhorses for extracting values.\ndecode(_:forKey:) assumes the value exists and is well-formed, so it either returns the decoded value or throws. Use it when the API contract says “this key is always there.” decodeIfPresent(_:forKey:) returns nil when the key is missing or explicitly null, letting you fall back to sensible defaults without leaking optionals throughout the call site. JSONDecoder will automatically assign nil to optional properties when you rely on the synthesized initializer, but only if the property itself is declared optional. In this example frequency is non-optional because the rest of the app expects a real value. Using decodeIfPresent lets you detect a missing key, supply a fallback (.daily), and still keep the property non-optional. If you called decode(_:forKey:) and the key or value was missing, decoding would throw instead of letting you inject a default.\nWhat Happens to init(from:) You never call init(from:) directly. When you ask JSONDecoder to decode a type, it checks whether that type implements init(from:). If it does, the decoder instantiates the Decoder context (containing the key/value containers) and invokes your initializer, passing that context in. If your type has no custom initializer, the compiler synthesizes one automatically. All you do is call decode(_:from:); the Decoder plumbing happens under the hood.\nHere is what Decoders.api().decode(NotificationSettings.self, from: settingsData) does behind the scenes:\ndecode builds a Decoder object with the raw bytes and your global strategies. The decoder sees NotificationSettings conforms to Decodable, so it calls your init(from:) (or the synthesized one if you didn\u0026rsquo;t write one). Inside init(from:), you pull data out of the keyed container and provide defaults. When the initializer returns, JSONDecoder hands you the fully initialized instance. So the initializer is “injected,” but only because it’s mandated by the Decodable protocol; the runtime simply honors the implementation you supplied.\nModeling Enumerated Values APIs often send constrained values that map cleanly to Swift enums. Prefer strongly typed enums over raw String or Int fields so you get compiler guidance and exhaustiveness checking.\n1. Direct String Backing { \u0026#34;path\u0026#34;: \u0026#34;https://cdn.entangled.dev/images/header.png\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;png\u0026#34; } struct RemoteImage: Codable { let path: URL let type: ImageType enum ImageType: String, Codable { case png case jpg case heic } } Because ImageType conforms to String and Codable, decoding fails fast when the backend introduces an unexpected value, helping you spot contract changes. When a payload truly allows free-form values, fall back to String or Int; otherwise, rely on enums to keep the domain explicit and safe.\n2. Mapping Backend Slugs to Expressive Cases Sometimes the backend uses values that do not align with Swift naming conventions. When the payload is still a single string, mirror CodingKeys by assigning the raw value directly:\n{ \u0026#34;kind\u0026#34;: \u0026#34;profile-picture\u0026#34; } enum AvatarKind: String, Codable { case profilePicture = \u0026#34;profile-picture\u0026#34; case organizationLogo = \u0026#34;organization-logo\u0026#34; case systemGenerated = \u0026#34;system-generated\u0026#34; } For richer enums that need custom logic during decoding or encoding, map them explicitly while keeping expressive case names.\n{ \u0026#34;path\u0026#34;: \u0026#34;https://cdn.entangled.dev/avatars/42.png\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;profile-picture\u0026#34; } struct Avatar: Codable { let path: URL let kind: Kind enum Kind: Codable { case profilePicture case organizationLogo case systemGenerated init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() switch try container.decode(String.self) { case \u0026#34;profile-picture\u0026#34;: self = .profilePicture case \u0026#34;organization-logo\u0026#34;: self = .organizationLogo case \u0026#34;system-generated\u0026#34;: self = .systemGenerated default: throw DecodingError.dataCorruptedError(in: container, debugDescription: \u0026#34;Unsupported avatar kind\u0026#34;) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .profilePicture: try container.encode(\u0026#34;profile-picture\u0026#34;) case .organizationLogo: try container.encode(\u0026#34;organization-logo\u0026#34;) case .systemGenerated: try container.encode(\u0026#34;system-generated\u0026#34;) } } } } decoder.singleValueContainer() is what you reach for when the JSON fragment you’re decoding is literally just a single primitive (a string, number, or bool) instead of an object with keys. Without it, you’d be forced to introduce fake CodingKeys or wrap the value in another type just to read it. The single-value container hands you that raw value directly so you can convert it into an enum case (or any other type) in one step. encoder.singleValueContainer() mirrors the idea when writing JSON, ensuring the enum encodes back to the original one-value payload.\nWhy does this work when the parent JSON (Avatar) also has a path key? JSONDecoder handles the outer object first. It builds a keyed container for Avatar, decodes path, and when it reaches kind it delegates to Kind.init(from:). That delegate initializer receives a nested decoder that already points at the \u0026quot;kind\u0026quot; value, so singleValueContainer() sees only that scalar string—\u0026quot;profile-picture\u0026quot;—instead of the whole object. Containers are scoped, so they never “see” sibling keys; the keyed container for Avatar keeps walking properties one-by-one.\nswitch try container.decode(String.self) then decodes the underlying string (throwing if it is missing or malformed) and immediately pattern-matches on the result. Each case maps the backend slug to the right Swift case, while the default branch surfaces unsupported values. This syntax keeps the decode + mapping logic tight and ensures the initializer exits once it finds a match.\nfunc encode(to encoder: Encoder) is the flip side of init(from:). Conforming to Codable implicitly means conforming to both Decodable and Encodable; implementing this method lets you control how the enum (or struct) writes back to JSON when you call JSONEncoder().encode(...).\n3. Capturing Unknown Cases If you need to keep the app resilient while still flagging surprises, add an unknown case that stashes the raw value for logging.\nenum ImageType: Codable { case png case jpg case heic case unknown(String) init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer().decode(String.self) switch value { case \u0026#34;png\u0026#34;: self = .png case \u0026#34;jpg\u0026#34;: self = .jpg case \u0026#34;heic\u0026#34;: self = .heic default: self = .unknown(value) } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .png: try container.encode(\u0026#34;png\u0026#34;) case .jpg: try container.encode(\u0026#34;jpg\u0026#34;) case .heic: try container.encode(\u0026#34;heic\u0026#34;) case .unknown(let raw): try container.encode(raw) } } } This approach guards you against backends that silently add new types while preserving diagnostics for analytics or crash logs. You can combine it with the explicit mapping technique above by routing the default branch to .unknown(value) instead of throwing.\nCustom Decoding Logic For more complex transformations, implement init(from:).\n{ \u0026#34;accent_color\u0026#34;: \u0026#34;FF0080\u0026#34; } struct Color: Codable { let red: Double let green: Double let blue: Double init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let hex = try container.decode(String.self) guard let rgb = Int(hex, radix: 16) else { throw DecodingError.dataCorruptedError( in: container, debugDescription: \u0026#34;Expected a hex string like FF0080\u0026#34; ) } red = Double((rgb \u0026gt;\u0026gt; 16) \u0026amp; 0xFF) / 255 green = Double((rgb \u0026gt;\u0026gt; 8) \u0026amp; 0xFF) / 255 blue = Double(rgb \u0026amp; 0xFF) / 255 } } let colorJSON = \u0026#34;\u0026#34;\u0026#34; { \u0026#34;accent_color\u0026#34;: \u0026#34;FF0080\u0026#34; } \u0026#34;\u0026#34;\u0026#34; let accent = try Decoders.api().decode([String: Color].self, from: Data(colorJSON.utf8)) print(accent[\u0026#34;accent_color\u0026#34;]?.red ?? 0) Here the payload is a hex string (\u0026quot;FF0080\u0026quot;) and the decoder must convert it into normalized color components. Throwing a DecodingError produces actionable diagnostics during development.\nDecoding Polymorphic Payloads When APIs send unions (one of several shapes under the same key), model them with enums that have associated values.\n{ \u0026#34;attachments\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;image\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://cdn.entangled.dev/image.png\u0026#34;, \u0026#34;width\u0026#34;: 640, \u0026#34;height\u0026#34;: 480 }, { \u0026#34;type\u0026#34;: \u0026#34;file\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;report.pdf\u0026#34;, \u0026#34;size_bytes\u0026#34;: 10240 }, { \u0026#34;type\u0026#34;: \u0026#34;code\u0026#34;, \u0026#34;language\u0026#34;: \u0026#34;swift\u0026#34;, \u0026#34;snippet\u0026#34;: \u0026#34;print(\\\u0026#34;Hello\\\u0026#34;)\u0026#34; } ] } enum Attachment: Codable { case image(Image) case file(File) case code(Code) struct Image: Codable { let url: URL let width: Int let height: Int } struct File: Codable { let name: String let sizeBytes: Int } struct Code: Codable { let language: String let snippet: String } private enum CodingKeys: String, CodingKey { case type } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let type = try container.decode(String.self, forKey: .type) switch type { case \u0026#34;image\u0026#34;: self = .image(try Image(from: decoder)) case \u0026#34;file\u0026#34;: self = .file(try File(from: decoder)) case \u0026#34;code\u0026#34;: self = .code(try Code(from: decoder)) default: throw DecodingError.dataCorruptedError( forKey: .type, in: container, debugDescription: \u0026#34;Unsupported attachment type: \\(type)\u0026#34; ) } } } Using enums keeps the call site type-safe. A switch on Attachment forces you to handle every variant and lets you opt in to unknown-case strategies where appropriate.\nMapping Nested Collections When the API wraps arrays under a key, decode them via nested containers.\n{ \u0026#34;data\u0026#34;: { \u0026#34;articles\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Swift Strings 101\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 4 }, { \u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Macros in Swift 5.9\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 7 } ] } } struct FeedResponse: Decodable { let articles: [Article] enum CodingKeys: String, CodingKey { case data } enum DataKeys: String, CodingKey { case articles } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataKeys.self, forKey: .data) articles = try dataContainer.decode([Article].self, forKey: .articles) } } Nested containers keep your models minimal while handling payload structures that mirror database responses or GraphQL-style edges.\nIf the nested payload carries multiple fields you care about, decode each sibling from the same container.\n{ \u0026#34;data\u0026#34;: { \u0026#34;articles\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Swift Strings 101\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 4 }, { \u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Macros in Swift 5.9\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 7 } ], \u0026#34;other_property\u0026#34;: \u0026#34;some value\u0026#34;, \u0026#34;featured_article\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Concurrency Deep Dive\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 12 } } } struct RichFeedResponse: Decodable { let articles: [Article] let featured: Article? let otherProperty: String enum CodingKeys: String, CodingKey { case data } enum DataKeys: String, CodingKey { case articles case featuredArticle = \u0026#34;featured_article\u0026#34; case otherProperty = \u0026#34;other_property\u0026#34; } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let dataContainer = try container.nestedContainer(keyedBy: DataKeys.self, forKey: .data) articles = try dataContainer.decode([Article].self, forKey: .articles) featured = try dataContainer.decodeIfPresent(Article.self, forKey: .featuredArticle) otherProperty = try dataContainer.decode(String.self, forKey: .otherProperty) } } Nested containers behave like scoped dictionaries; once you have one, read every relevant key—required or optional—before you leave it.\nIntegrating with Legacy [String: Any] Stores Some codebases funnel network payloads through a store that already parses JSON into [String: Any]. You can still lean on your Codable models instead of hand-casting dictionaries.\nlet storePayload: [String: Any] = [ \u0026#34;data\u0026#34;: [ \u0026#34;articles\u0026#34;: [ [\u0026#34;id\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Swift Strings 101\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 4], [\u0026#34;id\u0026#34;: \u0026#34;2\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Macros in Swift 5.9\u0026#34;, \u0026#34;read_time_minutes\u0026#34;: 7] ], \u0026#34;other_property\u0026#34;: \u0026#34;some value\u0026#34; ] ] func decodeFeed(from payload: [String: Any]) throws -\u0026gt; RichFeedResponse { guard JSONSerialization.isValidJSONObject(payload) else { throw NSError(domain: \u0026#34;Store\u0026#34;, code: 0, userInfo: [NSLocalizedDescriptionKey: \u0026#34;Invalid JSON object\u0026#34;]) } let data = try JSONSerialization.data(withJSONObject: payload) return try Decoders.api().decode(RichFeedResponse.self, from: data) } Converting back to Data lets you reuse the same decoders and validation logic while keeping the legacy store untouched. If you truly need to stay in dictionary space, encapsulate the nested lookups in a helper so the casting is localized.\nfunc articles(from payload: [String: Any]) throws -\u0026gt; [[String: Any]] { guard let data = payload[\u0026#34;data\u0026#34;] as? [String: Any], let articles = data[\u0026#34;articles\u0026#34;] as? [[String: Any]] else { throw NSError(domain: \u0026#34;Store\u0026#34;, code: 1, userInfo: [NSLocalizedDescriptionKey: \u0026#34;Articles missing\u0026#34;]) } return articles } The dictionary approach works, but it is fragile: missing keys collapse to nil, spelling mistakes slip through, and you duplicate parsing logic everywhere you need the data. Wrapping that plumbing in a helper at least centralizes the risk, but re-encoding to Data and letting Codable handle validation buys you type safety, better diagnostics, and parity with the rest of the codebase. Start with the helper as a stopgap, then migrate callers to the Codable path.\narticles ends up as [[String: Any]] because every element of the JSON articles array is a JSON object ({ ... }). When bridged to Swift without a model, each object becomes a [String: Any] dictionary containing the article’s fields (\u0026quot;id\u0026quot;, \u0026quot;title\u0026quot;, \u0026quot;read_time_minutes\u0026quot;, and so on). The surrounding array just preserves the list of those dictionaries until you map them into strong types.\nSurface Decoding Errors Clearly DecodingError carries rich context. Pattern-match on it to log actionable diagnostics instead of generic “data corrupted” messages.\nfunc log(_ error: Error) { switch error { case DecodingError.keyNotFound(let key, let context): print(\u0026#34;Missing key \\(key.stringValue):\u0026#34;, context.debugDescription) case DecodingError.typeMismatch(let type, let context): print(\u0026#34;Type mismatch for \\(type):\u0026#34;, context.debugDescription) case DecodingError.valueNotFound(let type, let context): print(\u0026#34;Expected value for \\(type):\u0026#34;, context.debugDescription) case DecodingError.dataCorrupted(let context): print(\u0026#34;Data corrupted:\u0026#34;, context.debugDescription) default: print(\u0026#34;Unexpected error:\u0026#34;, error.localizedDescription) } } Attach decoded context, request identifiers, or user IDs to this logging to make server/client contracts easier to debug.\nDecoding with Swift Concurrency func loadUser(from url: URL) async throws -\u0026gt; User { let (data, _) = try await URLSession.shared.data(from: url) return try Decoders.api().decode(User.self, from: data) } By returning the decoded type directly, your async functions stay composable. Propagating thrown errors lets the caller decide whether to retry, show an alert, or load fallback data. Pass the decoder in as an argument when you want to override strategies (e.g., JSON with UNIX timestamps).\nTesting Your Decoders Decode against local fixtures in unit tests to assert that the models match the API contract.\nfunc testUserDecoding() throws { let bundle = Bundle.module let url = try XCTUnwrap(bundle.url(forResource: \u0026#34;user_fixture\u0026#34;, withExtension: \u0026#34;json\u0026#34;)) let data = try Data(contentsOf: url) let decoder = Decoders.api() let user = try decoder.decode(User.self, from: data) XCTAssertEqual(user.favoriteLanguages.count, 3) XCTAssertEqual(user.profile.website.host, \u0026#34;entangled.dev\u0026#34;) } Testing with captured payloads surfaces breaking changes early and documents assumptions about optional versus required fields.\nWrap Up Swift\u0026rsquo;s Codable ecosystem covers the majority of JSON parsing scenarios: adopt Decodable models, configure JSONDecoder centrally, lean on enums for constrained values, and reach for custom initializers when you need defaults or polymorphism. Combine these patterns with targeted logging and fixture-based tests to keep API integrations resilient and easy to reason about.\n","date":"2025-10-23T03:44:31Z","permalink":"/p/parsing-json-with-swift/","title":"Parsing JSON with Swift"},{"content":"Debugging HTTPS calls on iOS and iPadOS usually means reaching for a TLS proxy that can show you encrypted payloads. Charles Proxy remains a reliable option because it integrates with Apple devices, supports on-the-fly rewriting, and lets you archive sessions for later analysis. This guide walks through the minimal setup to get your device routing traffic through Charles without breaking App Transport Security (ATS).\nPrerequisites Charles Proxy 4.6 or newer installed on your Mac. The iOS or iPadOS device connected to the same Wi‑Fi network as the Mac running Charles. Developer access to the app you are testing so you can safely tweak networking settings if needed. Configure the Wi‑Fi proxy on the device Open Settings → Wi‑Fi on your device and tap the ℹ︎ icon next to the active network. Scroll to Configure Proxy and choose Manual. Enter your Mac\u0026rsquo;s local IP address in Server. You can find it in System Settings → Network or by running ipconfig getifaddr en0 on macOS. Set Port to 8888 (Charles\u0026rsquo; default) and leave Authentication off unless you enabled it in Charles. Tap Save. Traffic now routes through your Mac for that Wi‑Fi network only. Keep Charles open; if the proxy stops listening, iOS calls will fail immediately with NSURLErrorCannotConnectToHost.\nInstall and trust the Charles SSL certificate Charles needs to present its own certificate so it can decrypt HTTPS. Apple treats any custom certificate as untrusted until you explicitly approve it.\nOn the device, open Safari and navigate to https://chls.pro/ssl. Safari downloads a configuration profile and shows an Profile Downloaded banner. Tap it or go to Settings → General → VPN \u0026amp; Device Management. Under Downloaded Profile, select Charles Proxy CA and tap Install. Confirm with your passcode and approve the warning prompts. Navigate to Settings → General → About → Certificate Trust Settings. Under Enable Full Trust For Root Certificates, toggle on Charles Proxy CA and confirm. Without this step, every HTTPS connection will fail due to trust errors. Once trusted, Charles can decrypt traffic for domains that permit re-signing. ATS exceptions are unnecessary unless the app pins certificates or forbids proxies.\nVerify the capture In Charles, hit the Start Recording button (red circle). Leave Proxy → macOS Proxy off when you only care about physical devices; toggle it on if you also want macOS apps or simulators to route through Charles. Launch your app and repeat the network flow you want to inspect. Requests should stream into Charles\u0026rsquo; session list. Double-click an entry to view request headers, response payloads, and TLS information. Use Tools → SSL Proxying Settings to add specific hosts if Charles reports SSL handshake failed. Add domains with wildcards (for example, api.example.com or *.example.com). If your traceroute stays empty, confirm the device still uses the same Wi‑Fi network and that no VPN profiles override your proxy settings.\nTroubleshooting tips Certificate pinning: Apps that implement SSL pinning will reject Charles\u0026rsquo; certificate. Disable pinning in a debug build or use a build flavor without pinning to capture traffic. HTTP/3 traffic: Charles downgrades HTTP/3 to HTTP/2. If your API strictly requires QUIC, test with Apple\u0026rsquo;s PacketLogger or consider Proxyman/mitmproxy with HTTP/3 support. Background sessions: Background URLSession traffic may not route through the proxy when the app is suspended. Keep the screen awake or use Instruments\u0026rsquo; HTTP template for background captures. Different networks: Switching Wi‑Fi networks clears the proxy configuration. Re-apply the manual proxy after every network change. Clean up when finished Return to Settings → Wi‑Fi → (ℹ︎) and set Configure Proxy back to Off (or Automatic). In Settings → General → About → Certificate Trust Settings, disable the Charles Proxy CA toggle. Optionally remove the profile from VPN \u0026amp; Device Management to keep the device\u0026rsquo;s trust store clean. Resetting both the proxy and certificate ensures future HTTPS requests use your production trust chain and keeps users from accidentally routing through a dormant proxy.\nWith these steps in place you can capture, inspect, and replay network flows directly from real devices, which is invaluable when simulator behavior diverges from field reports.\n","date":"2025-10-03T17:21:27Z","permalink":"/p/inspecting-your-network-traffic-in-apple-platforms/","title":"Inspecting your network traffic in Apple Platforms"},{"content":"Hey everyone! In this post, I want to talk about how we can add and manage different gestures in UIKit. This post will be updated as I explore more gesture interactions. When needed, I\u0026rsquo;ll also touch on accessibility considerations, especially for VoiceOver.\nLong Press Gesture UIKit handles long press gestures using a class called UILongPressGestureRecognizer. You can attach it to any UIView using the addGestureRecognizer() method.\nWith UILongPressGestureRecognizer, you can configure:\nThe action that will be executed (this method must be marked with @objc), The required duration before the gesture triggers, And other options, which you can explore through the class\u0026rsquo;s available properties and methods. Gesture States The UILongPressGestureRecognizer can be in one of several states:\n.possible: The default state before the gesture has been recognized. .began: The gesture has met the minimum duration and has started. .changed: The user’s finger has moved while still pressing. .ended: The user has lifted their finger, ending the gesture. .cancelled: The system canceled the gesture (e.g., an interruption). .failed: The gesture didn’t meet the criteria for recognition. Example Here is a simple example of a label with an attached long press gesture recognizer:\nimport UIKit class ViewController: UIViewController { private lazy var label: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = \u0026#34;Hello, World!\u0026#34; label.textAlignment = .center label.font = .systemFont(ofSize: 24, weight: .medium) label.isUserInteractionEnabled = true // Required to detect gestures on the label return label }() override func viewDidLoad() { super.viewDidLoad() setupUI() setupLongPressGesture(for: label) } } private extension ViewController { func setupUI() { view.backgroundColor = .systemBackground view.addSubview(label) NSLayoutConstraint.activate([ label.centerXAnchor.constraint(equalTo: view.centerXAnchor), label.centerYAnchor.constraint(equalTo: view.centerYAnchor), ]) } func setupLongPressGesture(for view: UIView) { let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)) recognizer.minimumPressDuration = 0.5 // Customize duration if needed view.addGestureRecognizer(recognizer) } @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) { switch gesture.state { case .began: label.text = \u0026#34;Long press!\u0026#34; case .ended: label.text = \u0026#34;Hello, World!\u0026#34; default: break } } } This is a great starting point for adding more complex gestures or chaining interactions. In future updates, I’ll cover tap gestures, swipes, and how to handle VoiceOver interactions effectively.\nThanks for reading!\n","date":"2025-06-21T00:14:26-06:00","permalink":"/p/handling-touch-gestures-in-uikit/","title":"Handling Touch Gestures in UIKit"},{"content":"Sous Vide Is the Combine of Cooking (and It\u0026rsquo;s Not Just a Joke) Have you ever moved from imperative to reactive, functional Combine code and thought:\n“Wait… why doesn’t everything work like this?”\nThat’s exactly how I felt after discovering sous vide cooking.\nSous vide isn’t just a trendy food technique. It’s a declarative, functional, reactive system for cooking. It replaces the chaos of heat guessing and overcooked edges with precise timing, temperature control, and fully composed flavour pipelines.\nAnd yes — it’s basically Combine, but for food.\nTraditional Cooking Is Imperative Most traditional cooking is full of guesswork, manual state management, and side effects. You:\nFlip meat constantly Poke it to check doneness Guess when it’s ready Hope the inside isn’t raw while the outside burns That’s imperative cooking — direct, stateful, and prone to edge-case disasters.\nvar isDone = false while !isDone { checkCenter() flipSteak() hopeItWorks() } Sous Vide Is Combine Now let’s look at sous vide — where you:\nSet the desired temperature (like defining a Publisher) Apply a precise duration (like chaining .debounce(for:)) Add aromatics and fats (like injecting dependencies) Finish with a sear (a side-effect terminal operator) Suddenly, your food becomes a purely defined stream of transformation:\nribeyePublisher .setTemperature(54) .forDuration(.hours(2)) .combineLatest(thyme) .map { sear($0) } .sink { plate($0) } That’s not just metaphor — that’s real cooking logic.\nIngredients Are Data — The Bag Is the DI Container Before your food enters the pipeline, it’s prepared with all its required dependencies:\nA protein with defined attributes: cut, fat content, target temp Aromatics like garlic or thyme Optional modifiers like butter, ghee, miso, or oil You seal this together in a sous vide bag — your immutable dependency container:\nlet protein = Protein(cut: .ribeye, temp: 54) let flavor = [thyme, garlic, butter] let bag = SousVidePackage(main: protein, additions: flavor) The bag acts like a sealed struct — containing everything your food needs to be transformed with zero mutation mid-process.\nOnce it enters the bath, it’s a pure, self-contained unit of flavour.\nCooking Pipelines: Combine-Style Breakdown Combine Operator Cooking Equivalent .setTemperature(_:) Declare target doneness .forDuration(_:) Sous vide cooking time .combineLatest Add infused flavours (herbs, fats) .map Sear, smoke, or torch the result .delay(for:) Resting time .sink Serve the final plate Sous vide lets you build entire meals this way. Want to run proteins and veggies at different temps? Just spin up two pipelines (containers + circulators), and compose your plate from multiple streams.\nWhy This Actually Matters This isn’t just a metaphor — it’s functionally better cooking.\nMost of the cooking process becomes purely declarative:\nyou describe the end state (target temperature, duration, flavour infusions), and the sous vide system ensures it reaches that state with precision.\nAnd yes, we still use imperative cooking — but only at the end.\nThe sear, the smoke, the torch — these are controlled, purposeful side effects, isolated to a small, well-defined step. That step is short, visible, and easy to get right.\nThis separation of concerns means:\nThe majority of the cooking is error-free and repeatable The only mutable phase is optional and cosmetic (crust, browning) You dramatically minimise the risk of failure, because 95% of the process is handled declaratively Just like a good software architecture, sous vide isolates the risky, stateful parts of the process, keeping them out of the core logic — and your food is better for it.\nStructuring Your Meal as Data With sous vide, you start thinking in food components:\nstruct Protein { let cut: Cut let weight: Grams let desiredTemp: Celsius let aromatics: [Herb] } Even flavour additions can be typed and quantified:\nprotocol Flavoring { associatedtype Unit var amount: Unit { get } } struct Salt: Flavoring { typealias Unit = Grams let amount: Grams } struct Thyme: Flavoring { typealias Unit = Sprigs let amount: Sprigs } Each ingredient can now express what it is and how much of it belongs in the bag, keeping your recipe data clean, predictable, and reusable.\nThe more you model your meals like this, the more you realise:\nyou’re not improvising — you’re declaring data, composing systems, and defining a pipeline that transforms raw input into delicious output.\nConclusion Sous vide didn’t just improve my meals — it changed the way I think about food.\nIt’s precise. It’s elegant. It’s reactive.\nIt respects the ingredient, avoids mutation, and lets me compose my meals with confidence.\nAnd perhaps most beautifully: with sous vide, you start the pipeline, walk away, and wait for the system to emit a ready-to-finish result — perfectly aligned with Combine’s flow.\nYou don’t poke or poll. You don’t micromanage.\nYou subscribe once, and the water does the rest.\nAll you need to do… is sink { searAndServe($0) }.\nCombine your flavours.\nCompose your meals.\nServe with intention.\nYou’re not just cooking.\nYou’re declaring dinner.\nPS: No sink was harmed in the making of this ribeye.\n","date":"2025-05-08T22:52:30-06:00","permalink":"/p/reactive-sous-vide-cooking/","title":"Reactive Sous Vide Cooking"},{"content":"Navigating SwiftUI with an Enum Router (Cyclical A → B → C Flow) — A Simple example Introduction In this post, we’ll explore a lightweight, type‑safe way to manage navigation in SwiftUI by modelling screens as an enum and driving transitions with closures. We’ll illustrate a simple cyclical flow through three views—A → B → C → A—and then dive into the pros and cons of this approach.\n1. Defining the Router State First, we define an enum with three cases—one for each screen. This enum is our “single source of truth” for navigation state:\n// Represents each screen in our app. enum AppScreen { case viewA case viewB(someData: String) // We can pass values to next screens case viewC } 2. The Router Protocol \u0026amp; Default Implementation Next, we encapsulate navigation logic in a router object. Here’s a minimal protocol and default router using @Observable (iOS 17+), so SwiftUI updates automatically when currentScreen changes:\nimport Observation protocol AppRouter { var currentScreen: AppScreen { get } func navigate(to screen: AppScreen) } @Observable final class DefaultAppRouter: AppRouter { private(set) var currentScreen: AppScreen = .viewA func navigate(to screen: AppScreen) { currentScreen = screen } } 3. The Navigation Container We centralise navigation in one SwiftUI container. It switches over router.currentScreen and injects closures into each view to advance the flow cyclically:\nimport SwiftUI struct CyclicalNavigationView: View { @State private var router = DefaultAppRouter() var body: some View { content } @ViewBuilder private var content: some View { switch router.currentScreen { case .viewA: ViewA { receivedData in // We capture the data send by ViewA. // We send the data to ViewB using the router. router.navigate(to: .viewB(someData: receivedData)) } // We get the data from previous state and inject it when creating ViewB. case .viewB(let someData): ViewB(dataFromViewA: someData) { router.navigate(to: .viewC) } case .viewC: ViewC { router.navigate(to: .viewA) } } } } 4. The Three Views Each view is oblivious to the overall navigation logic—it simply calls its onNext closure when the user taps a button:\nstruct ViewA: View { let onNext: (String) -\u0026gt; Void // The closure receives tha data we want to pass to the next state. var body: some View { VStack { Text(\u0026#34;View A\u0026#34;) Button(\u0026#34;Go to B\u0026#34;) { // We pass data to next state using the closure argument. onNext(\u0026#34;some Data from screen A\u0026#34;) } } } } struct ViewB: View { // We optionally received some data from the router previous state. let dataFromViewA: String? let onNext: () -\u0026gt; Void var body: some View { VStack { Text(\u0026#34;View B\u0026#34;) .font(.title) if let dataFromViewA { Text(dataFromViewA) .font(.caption) } Button(\u0026#34;Go to C\u0026#34;, action: onNext) } } } struct ViewC: View { let onNext: () -\u0026gt; Void var body: some View { VStack { Text(\u0026#34;View C\u0026#34;) Button(\u0026#34;Go to A\u0026#34;, action: onNext) } } } 5. How It Works Initial State The router starts at .viewA, so ViewA is presented. Advancing Tapping “Go to B” calls router.navigate(to: .viewB), updating currentScreen. SwiftUI recomputes the switch and shows ViewB. Optionally if data must be shared or passed, we can use associated values and closure arguments to pass the data between states. Cyclical Flow Similarly, B → C and C → A form a loop. There’s no back‑stack—each transition replaces the current screen. Here is the navigation working:\n6. Pros and Cons Pros: This enum‑and‑closure router is completely type‑safe and exhaustive—every possible screen must be handled in your switch, so you’ll never accidentally forget a case. All of your navigation state lives in one place, making it easy to reason about where the app is and simple to write unit tests against your navigate(to:) calls. Because each view only knows about its own onNext closure, your views stay decoupled from routing details and remain highly reusable. You can also test the router in isolation—just invoke navigate(to:) and assert that currentScreen changes as expected. Finally, modelling screens as an enum feels very “functional,” with no magic strings or identifiers floating around.\nCons: On the flip side, there’s no built‑in history or back‑stack: each transition outright replaces the current screen, so if you need “go back” behaviour you’ll have to roll your own stack. You also miss out on SwiftUI’s native push/pop animations and swipe‑back gestures, unless you add transitions manually or switch to NavigationStack. As your app grows, the central switch can become large and repetitive, leading to boilerplate that’s harder to maintain. Supporting deep‑linking or state restoration means you must write custom serialization for your enum state. And finally, this pattern is best suited to single, linear (or cyclical) flows—if you have branching paths, modals or nested stacks, you’ll likely need a more sophisticated coordinator.\n7. Conclusion This enum‑and‑closure router shines for simple, linear or cyclical flows. It’s clear, type‑safe and easy to test. However, if you need: •\tA back‑stack with swipe‑back animations •\tDeep‑linking or state‑restoration support •\tComplex branching or modal flows\nyou might migrate to SwiftUI’s NavigationStack (iOS 16+) or adopt a more full‑featured coordinator. For many lightweight apps—like our A → B → C cycle—this pattern is both elegant and practical.\n","date":"2025-04-06T07:43:36-06:00","permalink":"/p/state-driven-enum-navigation/","title":"State Driven Enum Navigation"},{"content":"Want to make your SwiftUI views more reusable, composable, and clean?\nThis post explores how to build your own generic containers using Swift\u0026rsquo;s type system and SwiftUI\u0026rsquo;s powerful @ViewBuilder + closure patterns.\nWhether you\u0026rsquo;re building custom layouts, DSL-like preview wrappers, or logic-driven composition — understanding generic containers is a key unlock.\n💡 SwiftUI Tip: Wrapping Views with Generics and Closures I recently hit a situation in SwiftUI that seemed small at first — but led to a mini “aha!” moment about closures, generics, and how SwiftUI composes views.\nThis is one of those tricks that’s easy to overlook until you need it — but once it clicks, it unlocks a whole new layer of composability.\n🚧 The Problem I wanted to create a wrapper for SwiftUI previews — one that adds common styling like background color and full-frame layout, so I wouldn’t have to repeat this over and over:\n#Preview { VStack { MyCoolView() } .frame(maxWidth: .infinity, maxHeight: .infinity) .appBackground() } Simple enough, right?\nSo I tried to create a wrapper like this:\nstruct PreviewWrapperView: View { let content: some View // ❌ Error! var body: some View { VStack { content } .frame(maxWidth: .infinity, maxHeight: .infinity) .appBackground() } } But Swift gave me the error:\n\u0026ldquo;Property declares an opaque return type, but has no initializer expression\u0026hellip;\u0026rdquo;\n🔍 The Fix: Use a Generic + Closure Swift doesn’t allow some View as a stored property type — it only works for return values.\nThe correct pattern looks like this:\nstruct PreviewWrapperView\u0026lt;Content\u0026gt;: View where Content: View { @ViewBuilder let content: () -\u0026gt; Content var body: some View { VStack { content() } .frame(maxWidth: .infinity, maxHeight: .infinity) .appBackground() } } And then you use it like this:\n#Preview { PreviewWrapperView { MyCoolView(param: 123) } } 🤯 But wait — doesn’t MyCoolView(param: 123) need a closure like (Int) -\u0026gt; some View? This was the part that clicked for me.\nNo.\nYou’re not passing MyCoolView as a function.\nYou’re calling it inside the closure, and returning the result.\nThat means you’re passing a closure like:\n() -\u0026gt; MyView Which matches () -\u0026gt; Content just fine.\n🧠 What this unlocked for me I realized this pattern is everywhere in SwiftUI:\nNavigationStack { ... } Section(header: Text(\u0026quot;...\u0026quot;)) { ... } Button { ... } label: { ... } Once you know how to create your own view wrapper using a generic and a closure, you can build anything from:\nDSL-style layout containers Reusable environment-aware wrappers Onboarding and modal flows Themed previews Stylized layout shells with safe areas, animations, shadows\u0026hellip; 🧪 Bonus: Custom Layout Containers Now that you understand how to use closures and generics to wrap views, here\u0026rsquo;s a real-world way to apply this pattern for styling and layout reuse.\nLet\u0026rsquo;s say you want a layout that wraps your screen content in consistent padding, background, and spacing — and optionally adds a footer.\nAt first, you might be tempted to make the footer closure optional, or try to hack it using AnyView or Footer? tricks. But there\u0026rsquo;s a cleaner, more Swifty way — and it\u0026rsquo;s the way Apple does it too.\nInstead of creating multiple types, Apple defines multiple initializers in the same struct — and we can do the same.\nHere is the wrapper using all we have learned today!\nstruct StyledScreen\u0026lt;Content: View, Footer: View\u0026gt;: View { let content: Content let footer: Footer? init( @ViewBuilder content: () -\u0026gt; Content ) where Footer == EmptyView { self.content = content() self.footer = nil } init( @ViewBuilder content: () -\u0026gt; Content, @ViewBuilder footer: () -\u0026gt; Footer ) { self.content = content() self.footer = footer() } var body: some View { VStack(spacing: 24) { content .padding() .background(Color.secondary.opacity(0.1)) .cornerRadius(12) if let footer = footer { footer .padding(.top, 32) } } .padding() .overlay( RoundedRectangle(cornerRadius: 16) .stroke(.gray.opacity(0.3), style: StrokeStyle(lineWidth: 1, dash: [6])) ) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.systemBackground)) } } 📦 Usage #Preview { VStack { // With no footer StyledScreen { Text(\u0026#34;No footer\u0026#34;) } // With footer StyledScreen { Text(\u0026#34;Has a footer\u0026#34;) } footer: { Button(\u0026#34;Continue\u0026#34;) { } } } } This version matches Apple’s API design philosophy.\nHere is the result:\n🤔 What\u0026rsquo;s up with where Footer == EmptyView? This line:\ninit(@ViewBuilder content: () -\u0026gt; Content) where Footer == EmptyView might look strange at first — but it’s key to enabling a clean, SwiftUI-style API.\n💡 Why it exists: Our StyledScreen struct uses two generics:\nstruct StyledScreen\u0026lt;Content: View, Footer: View\u0026gt; That means Swift needs to know both types — even if the user doesn’t explicitly pass a footer.\nIf we don’t specify what Footer is, Swift will throw an error like:\n\u0026ldquo;Cannot infer generic parameter \u0026lsquo;Footer\u0026rsquo; without more context\u0026rdquo;\nTo fix that, we say:\n\u0026ldquo;If the user only provides content, then assume Footer == EmptyView.\u0026rdquo;\nThis satisfies the compiler and lets us write:\nStyledScreen { Text(\u0026#34;Hello\u0026#34;) } Without needing to manually pass an EmptyView.\nThis pattern is used inside SwiftUI itself — like in NavigationLink, Button, and Section — to support clean, overloaded initializers while keeping everything fully type-safe.\n📦 TL;DR Use Content: View + () -\u0026gt; Content to pass view content into your wrapper Use @ViewBuilder if you want to support multiple children Don’t try to store some View — use a closure instead This unlocks powerful layout composition in SwiftUI ✨ Hope this helped someone else!\nIf you’ve used this pattern in an interesting way, or have a cool layout abstraction — let me know! I’d love to see what you\u0026rsquo;re building.\n","date":"2025-04-06T05:20:12-06:00","permalink":"/p/swiftui-generic-containers/","title":"SwiftUI Generic Containers"},{"content":"I\u0026rsquo;m a total fan of immutable Operating System. the peace of mind, stability, and consistency they provide is invaluable but getting started can be a bit difficult, specially if you\u0026rsquo;re coming for more traditional operating systems (mutable ones).\nWith this post I aim to show my current workflow working on Fedora Silverblue, I am by no means an expert on Fedora SilverBlue. I want to share the way I use these systems and I hope to helps other by sharing my current understanding and workflows.\nI intend to update this post with new workflows and ideas that helped me to create this new mental map for stateless operating systems. I wish you find this useful.\nHow to install VSCode As a developer I find VSCode quite useful for a myriad of programming task, so a natural question arise: How do we install it on Fedora Silverblue?\nNormally we would add the official Microsoft repository and install it using dnf but in Silverblue we don\u0026rsquo;t have access to dnf, instead we have rpm-ostree. rpm-ostree is a tool used to layer other software on top of your current Fedora image. But I find this not ideal since we will start to pollute our host OS, a better alternative is using Toolboxes.\ntoolbox is a tool used to create separated mutable containers where we can install our tools, normally used to install CLI software is also a great alternative to install our IDE.\nThe magic of toolbox is that it creates a separate mutable environment for us to install and use software that was not installed by default in our stateless operating system. This also apply to the repositories added to the system, each toolbox contains a separate list of repositories, independent of each other, even from the host.\nBear in mind these containers are not completely isolated from the host, as a consequence you cannot install mindlessly any unknown software thinking it\u0026rsquo;s isolated and safe to run. Please be cautious and treat the security inside these containers the same as you would with you local host system.\nThese toolboxes won\u0026rsquo;t pollute our main operating system and once we delete them, all the installed apps will be gone and our system will remain clean and stable. There is one catch, containers have by default access to our $HOME directory, so if the program creates configurations or files in this directory they will remain after the deletion of the container (you can delete these files if needed).\nAfter this introduction let\u0026rsquo;s start with the installation of VSCode inside a toolbox.\nCreating the toolbox Let\u0026rsquo;s start by creating a new toolbox container, open a new terminal on the host OS and type the following:\nTo create the toolbox we enter:\nYou can give any name to your toolbox by replacing programming-box.\ntoolbox create programming-box To enter the toolbox we type:\ntoolbox enter programming-box The terminal prompt will add a small hexagon, this tell us we\u0026rsquo;re inside the toolbox.\nInstalling VSCode inside the toolbox Now that we are inside the toolbox we can use is as a regular mutable OS, inside the toolbox we have access to dnf. This mean that the installation steps are the same as the official guide from Microsoft.\nInside the toolbox run the following commands to add the official repository and install the software.\nAdd the official repository by running this inside the toolbox.\nsudo rpm --import https://packages.microsoft.com/keys/microsoft.asc echo -e \u0026#34;[code]\\nname=Visual Studio Code\\nbaseurl=https://packages.microsoft.com/yumrepos/vscode\\nenabled=1\\ngpgcheck=1\\ngpgkey=https://packages.microsoft.com/keys/microsoft.asc\u0026#34; | sudo tee /etc/yum.repos.d/vscode.repo \u0026gt; /dev/null Update the repository list to get the newest software available.\ndnf check-update Install VSCode inside the toolbox.\nsudo dnf install code # or code-insiders Running VSCode You may noticed that after installing the software you cannot find it in the installed apps, this is due to the nature of the isolated containers. The app is only accessible from inside the toolbox, so a system wide access is not possible.\nIf you want a system-wide access to the software, you could consider using a community-driven Flatpak or layering the app using rpm-ostree. I would not recommend layering unless is strictly necessary for your use case, and using a Flatpak may impose some extra steps to access the tools inside your toolboxes. Installing via dnf inside a toolbox seems like the best solution for most cases.\nFlatpaks are the recommended way to install GUI apps in Fedora Silverblue but sometimes this way of working may create conflicts with other tools. For programming I find having everything I need (from toolchains to IDEs) inside a toolbox a hassle free solution.\nTo run VSCode, enter the toolbox container where the app is installed and run the following.\ncode This will open the VSCode GUI and you can use it as usual.\nYou can install your programming toolchains inside the toolbox that contains VSCode and start using VSCode with these tools.\nConsiderations You can follow this steps to install any tool you need. For example, GitHub CLI tools are not included by default in Fedora repositories, so we can add the repository and install the software in a similar fashion as we did with VSCode.\nOther CLI tools that are part of Fedora repositories can be installed easily inside a toolbox by using sudo dnf install app_name.\nConclusion Immutable Operating Systems are a great way to get rock solid and performant systems without worrying about updates or dependency problems. They container workflow is a great way to separate your tools and projects.\nBy using toolboxes you can add and configure your toolchains and programs as you would in a Mutable OS, with the adding benefit that once you\u0026rsquo;re done you can delete the changes and your system will stay clean and secure.\nI wish these small how\u0026rsquo;s to helps you in your journey towards immutability.\n","date":"2024-12-26T06:14:43-05:00","permalink":"/p/fedora-silverblue-toolbox-101/","title":"Fedora Silverblue Toolbox 101"},{"content":"Introduction Hey everyone, I\u0026rsquo;m back with a new entry. This time, I\u0026rsquo;d like to guide you on how to create a simple hello world program for the ESP32-C6 inside a Virtual Machine.\nHaving all the toolchain inside a VM is a great way to unclutter your host machine. Programming inside a VM or containers for web development is quite common, but for microcontrollers, it\u0026rsquo;s a thing I don\u0026rsquo;t encounter that often.\nWhat we need For this tutorial, we will need:\nFedora Server 41 ARM UTM ESP32-C6 USB-C cable Creating the Virtual Machine Let\u0026rsquo;s start by creating the Virtual Machine. To archive this, you need a software capable of virtualizing your OS environment. Get UTM from the macOS Apple Store or directly from their website.\nWe also need to get the ISO we are going to use to create the VM. In this tutorial, we are using Fedora Server. Download the ARM ISO from here.\nWe will follow similar steps to install the OS similar to the ones on this post I made some time ago. We can follow along, with the difference that we are selecting Virtualize this time.\nOnce the OS is ready and SSH is working, let\u0026rsquo;s install the dependencies!\nInstalling dependencies inside the VM Once you\u0026rsquo;re inside the VM, let\u0026rsquo;s run the following commands:\n# Update the virtual OS sudo dnf update -y # Installing the needed software sudo dnf install -y python3 python3-pip # Install the udev needed to connect to the microcontrollers curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules # Install PlatformIO Core CLI curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py python3 get-platformio.py We are using the PlatformIO CLI because the graphical interface installed by the plugin doesn\u0026rsquo;t work by default inside the VM. As a workaround, you could run pio home --host 0.0.0.0 --port 8008 to access the VSCode plugin GUI interface from your host browser.\nCreating our basic project template Once we have finished installing the tooling, we can start creating our hello world project.\nAs a first step, let\u0026rsquo;s activate the Python environment created by PlatformIO, to achieve that run:\n# Activate the Python environment for PlatformIO source ~/.platformio/penv/bin/activate Then let\u0026rsquo;s create our project, navigate to the directory where you want to host the project, and run the following:\n# Find the name of your microcontroller from the supported list, copy this name pio boards | grep \u0026#34;YOUR_BOARD_NAME_HERE\u0026#34; # We use pio to create the basic template project pio project init --project-dir . --board \u0026#34;YOUR_BOARD_NAME_HERE\u0026#34; # Open your project in VSCode code . As simple as that, we have our base project ready for us, now let\u0026rsquo;s do some code!\nProgramming the Hello World Once you have opened the project, open the file called main.c located inside /src/ and add the following:\n#include \u0026lt;stdio.h\u0026gt; #include \u0026#34;freertos/FreeRTOS.h\u0026#34; #include \u0026#34;freertos/task.h\u0026#34; void app_main() { while (true) { printf(\u0026#34;Hello world\\n\u0026#34;); vTaskDelay(1000 / portTICK_PERIOD_MS); } } The ESP32-C6 is an ARM device, so we are using the Espressif framework instead of the Arduino framework. Check with your manufacturer the type of architecture your board uses.\nThis program will print to the serial bus the string Hello world\\n every second.\nWe also need to specify the baud rate so that we can monitor the output correctly. Open the file called platformio.ini and append at the end the following line:\nmonitor_speed = 115200 With all this done, we are one step closer to seeing our awesome code working!\nCompiling, flashing, and monitoring the microcontroller Now it\u0026rsquo;s time to connect our board via USB to the VM:\nConnect the board using USB to your host machine. Open the VM window in UTM and, in the top right, press the USB plug icon and connect your board to the VM. Check that the board is read by Fedora by running lsusb. You should see the same name that appeared on the UTM menu. We can use the PlatformIO extension to make the next steps easier. Install the extension inside the VM. After reloading, you will see at the bottom left these 3 icons: check mark, right arrow, and electric plug.\nNow let\u0026rsquo;s do the final steps!\nPress the check mark to build the project. Press the right arrow to flash your board (some boards may need to press some buttons on the board to activate the flash mode). Press the electric plug icon to see the hello world string being sent to the serial monitor every second! Congratulations, now you can start developing inside a VM for your microcontrollers.\nConclusion Developing for microcontrollers inside a virtual machine not only helps keep your host system clean and organized but also creates a portable and reproducible development environment. By following this guide, you’ve set up a Fedora Server VM with all the necessary tools to program an ESP32-C6, created a basic “Hello World” project, and successfully flashed it to your board.\nThis approach combines the flexibility of modern tools like PlatformIO with the advantages of virtualization, providing a robust setup for any microcontroller project. Whether you’re just starting with embedded systems or looking for a cleaner workflow, this method proves that virtual machines are a practical solution for microcontroller development.\nNow that you’ve mastered the basics, you can explore more complex projects, experiment with different boards, or even automate your setup further. Happy coding, and enjoy building your next microcontroller masterpiece!\n","date":"2024-11-25T03:21:54Z","permalink":"/p/101-guide-to-microcontroller-programming-inside-virtual-machines/","title":"101 Guide To Microcontroller Programming Inside Virtual Machines"},{"content":"ARM processors are becoming more ubiquitous thanks to the introduction of Apple Silicon and Qualcomm Snapdragon. These new processors offer great performance, silent operation, and all-day battery life. On the other hand, not all software is ported to this new architecture, so there could be cases when we need to run or program x86_64 devices.\nTo solve this problem, we have several tools at our disposal. In this post, let\u0026rsquo;s explore one solution to emulate and run x86_64 software.\nWhat we need An ARM64 machine. In our case, any Apple Silicon Mac would suffice. UTM App. Any x86_64 OS ISO. In our case, we\u0026rsquo;re installing Ubuntu Server 24.10. Installing UTM The piece of software that will be doing the work for us is called UTM. This is a macOS app that uses QEMU under the hood for virtualization and emulation.\nWe can get this software from their website or directly from the macOS App Store. I find the former option the best.\nChoose the option that best suits your needs.\nCreating the emulated Virtual Machine Once we have UTM installed, let\u0026rsquo;s download the ISO of the operating system we want to emulate. In our case, Ubuntu Server 24.10.\nNext, let\u0026rsquo;s open UTM and click on Create a New Virtual Machine. Here, we just need to select emulation, select the downloaded ISO, and configure the virtualized hardware. The process is quite straightforward.\nIn the last screen, you can view the summary of the VM. If this looks good to you, press Create and start the VM on the main UTM window.\nThis will start the normal installation of the selected OS using the provided ISO.\nIt\u0026rsquo;s a good idea to install OpenSSH for Ubuntu Server.\nConfiguring SSH Once we are logged into our VM, we can start configuring SSH. This will allow us to access our VM from our host OS and connect via VSCode to start programming.\nFirst, we need to create our SSH key. For that, we can follow the GitHub tutorial here.\nOnce we have the SSH key ready, we can connect to the VM.\nThe previously added SSH server list is located in ~/.ssh/config\nOn VM, get the IP address using ip a. The IP should look similar to inet 192.168.1.10/24 (we don\u0026rsquo;t need the ending /24 part, just the numbers). On macOS terminal, shh into the vm machine using ssh username@vm_ip_address. If your VM doesn\u0026rsquo;t have OpenSSL installed, run the following inside your VM sudo apt update \u0026amp;\u0026amp; sudo apt install openssh-server \u0026amp;\u0026amp; sudo systemctl start ssh \u0026amp;\u0026amp; sudo systemctl enable ssh \u0026amp;\u0026amp; sudo systemctl status ssh. This will install SSH, run it, enable it on startup, and check its current status.\nCoding an ASM example with VSCode Now it\u0026rsquo;s time to do some x86_64 ASM to verify that our system is working properly.\nOpen VSCode and click on the lower left blue icon \u0026gt;\u0026lt;. Select + Add New SSH Host... and add your VM host parameters using this structure ssh username@vm_ip_address. Select the configuration file to save your new host (the first option is okay). Once the host is added, click on Connect. (On macOS, you may need to give VSCode permission to access the network, allow it, and press retry). Wait for the VSCode server to install on the VM. Now that everything is ready, let\u0026rsquo;s make a hello world!\nOpen VSCode integrated terminal (CMD + BACKTICK) and create a new file called hello.asm (use touch hello.asm to create the file and code hello.asm to open the file on VSCode) with the following content:\nsection .data msg db \u0026#39;Hello, World!\u0026#39;, 0xA ; Message to print with newline len equ $ - msg ; Length of the message section .text global _start ; Entry point for the program _start: mov rax, 1 ; sys_write system call (1) mov rdi, 1 ; File descriptor (stdout) mov rsi, msg ; Address of the message mov rdx, len ; Length of the message syscall ; Make the system call mov rax, 60 ; sys_exit system call (60) xor rdi, rdi ; Exit code 0 syscall ; Make the system call Before continuing, install the assembler using sudo apt install nasm.\nNow let\u0026rsquo;s run the program!\n# Assembling the code nasm -f elf64 hello.asm -o hello.o # Linking the code ld hello.o -o hello # Running the code ./hello If all went well, you should see on the integrated terminal the text Hello, World!. Congratulations, your emulated x86_64 system is working correctly!\nConclusion ARM processors have revolutionized the computing landscape with their efficiency, performance, and battery life, making them an excellent choice for modern devices. However, their growing adoption doesn\u0026rsquo;t eliminate the need to run or develop software for the x86_64 architecture. With tools like UTM, we can easily emulate x86_64 systems on ARM-based devices, such as Apple Silicon Macs.\nThis post guided you through the process of setting up UTM, creating a virtual machine, configuring SSH, and even running an x86_64 assembly program. By following these steps, you\u0026rsquo;ve not only learned how to emulate a different architecture but also created a functional environment for programming and development.\nThe flexibility of emulation tools bridges the gap between architectures, ensuring that developers can maintain productivity regardless of the hardware they are using. This workflow demonstrates how we can adapt to and leverage the best of both ARM and x86_64 worlds. Happy coding!\n","date":"2024-11-19T07:31:29Z","permalink":"/p/emulating-ubuntu-server-x86_64-on-macos-arm64/","title":"Emulating Ubuntu Server X86_64 on macOS ARM64"},{"content":"Welcome to another installment of UIKit Learning! Today, we will dive into UICollectionView and explore how to use it effectively in your iOS apps.\nA UICollectionView is a versatile and powerful component in UIKit that allows you to present a grid or list of items in a highly customizable layout. Whether you need a simple grid of images, a complex layout with multiple sections and headers, or dynamic, animated updates, UICollectionView provides the flexibility to create engaging user interfaces.\nWe’ll start with a minimal example and gradually build up to more complex scenarios, using the latest best practices and features, including diffable data sources (a favorite of mine).\nSetting Up a Minimal UICollectionView Creating the base view controller As usual, we\u0026rsquo;re going to approach our development programmatically, so you can refer to my post on setting up a programmatic UIKit project.\nLet\u0026rsquo;s start by creating a new file called ViewController.swift and add the following code.\nimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } } This is the minimal view controller we are going to use to display the collection.\nWe are using this controller as the root ViewController for window in Scene Delegate.\nAdding the collection to the view Now it\u0026rsquo;s time to add our UICollectionView, inside the ViewController add the following property.\nimport UIKit class ViewController: UIViewController { private var collectionView: UICollectionView! // add this override func viewDidLoad() { super.viewDidLoad() } } Next, let\u0026rsquo;s configure the collection, by adding and calling a configure function.\nclass ViewController: UIViewController { private var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() setupCollectionView() // 2. call the function } // 1. create this function func setupCollectionView() { // Creating layout let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 100, height: 100) layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) layout.scrollDirection = .horizontal // creating actual collection collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .darkGray // adding to view view.addSubview(collectionView) // adding constraints collectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) ]) } } This functions creates the actual collection and the layout for the collection.\nWhy we need a collectionViewLayout When working with UICollectionView, you need to provide a layout object that determines how the items in the collection view are arranged.\nUICollectionViewFlowLayout is a concrete layout object that organizes items into a grid with optional header and footer views for each section. It’s the most commonly used layout class because it provides a lot of built-in functionality that suits many common use cases.\nUICollectionView does not come with a default layout. You must provide a layout object for it to work. UICollectionViewFlowLayout is the default layout provided by UIKit that supports basic grid-like arrangements.\nAdding the data source Our collection is already created with a basic layout but we need to have actual data to make it display any information, to solve this problem we could implement the UICollectionViewDataSource but I prefer to use a diffable data source.\nWhat\u0026rsquo;s a diffable data source A diffable data source is a modern way to manage the data in your collection views and table views. It makes it easier to update your UI when your data changes, ensuring smooth and efficient updates without having to manually calculate the differences between the old and new data.\nIn simple terms, you provide the entire set of new data to the data source using a snapshot, then the data source internally manages the update without any intervention from the user. A snapshot is like a picture of your data at a particular moment.\nCreating the diffable data source Inside the class let\u0026rsquo;s create a new property called dataSource.\nclass ViewController: UIViewController { private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource\u0026lt;Int, String\u0026gt;! // add this // More code ... The provided types are for the section identifier and the actual data our collection will display, in our case we are using Int as the section identifier and String as the data we want to display. We could use any custom types as long as they are valid for the role.\nConfiguring the data source The next step is crucial for our collection to work, we need to configure how the cells will be updated and we need to register the collection in our data source.\nLet\u0026rsquo;s add the following function to our class\nclass ViewController: UIViewController { // more code ... override func viewDidLoad() { super.viewDidLoad() setupView() setupCollectionView() setupDataSource() // 2. call the function } // 1. create the function private func setupDataSource() { // configuring cell with diffable data source collectionView.register(CustomCollectionCell.self, forCellWithReuseIdentifier: CustomCollectionCell.name) // configure cell update dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, name in guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionCell.name, for: indexPath) as? CustomCollectionCell else { fatalError(\u0026#34;Unable to dequeue CustomCollectionCell\u0026#34;) } cell.backgroundColor = .orange cell.update(with: name) return cell } } // more code ... } First, we are registering the cell we are going to use in our collection.\nNext, creating the diffable data source and passing the reference to our collection. By doing this we are telling our data source the collection it will be managing.\nThe closure we are passing tells the data source how the update of each cell will be managed. Inside the closure we have access to the actual collection, the indexPath of the current element and the actual data element we want to display inside the cell.\nBy doing these simple steps we have our collection connected with its data source.\nCreating the snapshot Finally we need a way to update the collection data, to do this let\u0026rsquo;s add a new function that will handle the snapshot.\nclass ViewController: UIViewController { // more code ... // updating the diffable with new data func updateSnapshot(with data: [String]) { var snapshot = NSDiffableDataSourceSnapshot\u0026lt;Int, String\u0026gt;() snapshot.appendSections([0]) snapshot.appendItems(data) // Adding more items dataSource.apply(snapshot, animatingDifferences: true) } // more code ... } This function creates a snapshot and applies the new data to the underlying system that will automatically updates the collection for us.\nIf your collection wants to show different data, you need to update the snapshot and the data source to be compatible. The data source and snapshot must be of the same type to work.\nCreating a custom cell By default, collection cell doesn\u0026rsquo;t have text or other elements, so let\u0026rsquo;s create a custom cell we can reuse in our collection. You already register this cell for the collection and you\u0026rsquo;re already using it in the data source. It\u0026rsquo;s a simple cell that only shows a centered label.\nfinal class CustomCollectionCell: UICollectionViewCell { static let name = \u0026#34;CustomCollectionCell\u0026#34; private var label: UILabel! override init(frame: CGRect) { super.init(frame: frame) setupLabel() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } func update(with data: String) { label.text = data } private func setupLabel() { label = UILabel() label.textAlignment = .center self.contentView.addSubview(label) // remember to add it to the contentView label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ label.topAnchor.constraint(equalTo: contentView.topAnchor), // we are referring to contentView not view label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), // we are referring to contentView not view label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), // we are referring to contentView not view label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), // we are referring to contentView not view ]) } } In cells you need to add sub elements to the contentView not the view.\nTesting the collection Let\u0026rsquo;s test the collection. We need some data to show, here is some data you can add to your class.\nclass ViewController: UIViewController { private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource\u0026lt;Int, String\u0026gt;! private var mockData: [String] { [ \u0026#34;Bulbasaur\u0026#34;, \u0026#34;Ivysaur\u0026#34;, \u0026#34;Venusaur\u0026#34;, \u0026#34;Charmander\u0026#34;, \u0026#34;Charmeleon\u0026#34;, \u0026#34;Charizard\u0026#34;, \u0026#34;Squirtle\u0026#34;, \u0026#34;Wartortle\u0026#34;, \u0026#34;Blastoise\u0026#34;, \u0026#34;Caterpie\u0026#34;, \u0026#34;Metapod\u0026#34;, \u0026#34;Butterfree\u0026#34;, \u0026#34;Weedle\u0026#34;, \u0026#34;Kakuna\u0026#34;, \u0026#34;Beedrill\u0026#34;, \u0026#34;Pidgey\u0026#34;, \u0026#34;Pidgeotto\u0026#34;, \u0026#34;Pidgeot\u0026#34;, \u0026#34;Rattata\u0026#34;, \u0026#34;Raticate\u0026#34;, \u0026#34;Spearow\u0026#34;, \u0026#34;Fearow\u0026#34;, \u0026#34;Ekans\u0026#34;, \u0026#34;Arbok\u0026#34;, \u0026#34;Pikachu\u0026#34;, \u0026#34;Raichu\u0026#34;, \u0026#34;Sandshrew\u0026#34;, \u0026#34;Sandslash\u0026#34;, \u0026#34;Nidoran♀\u0026#34;, \u0026#34;Nidorina\u0026#34;, \u0026#34;Nidoqueen\u0026#34;, \u0026#34;Nidoran♂\u0026#34;, \u0026#34;Nidorino\u0026#34;, \u0026#34;Nidoking\u0026#34;, \u0026#34;Clefairy\u0026#34;, \u0026#34;Clefable\u0026#34;, \u0026#34;Vulpix\u0026#34;, \u0026#34;Ninetales\u0026#34;, \u0026#34;Jigglypuff\u0026#34;, \u0026#34;Wigglytuff\u0026#34;, \u0026#34;Zubat\u0026#34;, \u0026#34;Golbat\u0026#34;, \u0026#34;Oddish\u0026#34;, \u0026#34;Gloom\u0026#34;, \u0026#34;Vileplume\u0026#34;, \u0026#34;Paras\u0026#34;, \u0026#34;Parasect\u0026#34;, \u0026#34;Venonat\u0026#34;, \u0026#34;Venomoth\u0026#34;, \u0026#34;Diglett\u0026#34;, \u0026#34;Dugtrio\u0026#34;, \u0026#34;Meowth\u0026#34;, \u0026#34;Persian\u0026#34;, \u0026#34;Psyduck\u0026#34;, \u0026#34;Golduck\u0026#34;, \u0026#34;Mankey\u0026#34;, \u0026#34;Primeape\u0026#34;, \u0026#34;Growlithe\u0026#34;, \u0026#34;Arcanine\u0026#34;, \u0026#34;Poliwag\u0026#34;, \u0026#34;Poliwhirl\u0026#34;, \u0026#34;Poliwrath\u0026#34;, \u0026#34;Abra\u0026#34;, \u0026#34;Kadabra\u0026#34;, \u0026#34;Alakazam\u0026#34;, \u0026#34;Machop\u0026#34;, \u0026#34;Machoke\u0026#34;, \u0026#34;Machamp\u0026#34;, \u0026#34;Bellsprout\u0026#34;, \u0026#34;Weepinbell\u0026#34;, \u0026#34;Victreebel\u0026#34;, \u0026#34;Tentacool\u0026#34;, \u0026#34;Tentacruel\u0026#34;, \u0026#34;Geodude\u0026#34;, \u0026#34;Graveler\u0026#34;, \u0026#34;Golem\u0026#34;, \u0026#34;Ponyta\u0026#34;, \u0026#34;Rapidash\u0026#34;, \u0026#34;Slowpoke\u0026#34;, \u0026#34;Slowbro\u0026#34;, \u0026#34;Magnemite\u0026#34;, \u0026#34;Magneton\u0026#34;, \u0026#34;Farfetch\u0026#39;d\u0026#34;, \u0026#34;Doduo\u0026#34;, \u0026#34;Dodrio\u0026#34;, \u0026#34;Seel\u0026#34;, \u0026#34;Dewgong\u0026#34;, \u0026#34;Grimer\u0026#34;, \u0026#34;Muk\u0026#34;, \u0026#34;Shellder\u0026#34;, \u0026#34;Cloyster\u0026#34;, \u0026#34;Gastly\u0026#34;, \u0026#34;Haunter\u0026#34;, \u0026#34;Gengar\u0026#34;, \u0026#34;Onix\u0026#34;, \u0026#34;Drowzee\u0026#34;, \u0026#34;Hypno\u0026#34;, \u0026#34;Krabby\u0026#34;, \u0026#34;Kingler\u0026#34;, \u0026#34;Voltorb\u0026#34;, \u0026#34;Electrode\u0026#34;, \u0026#34;Exeggcute\u0026#34;, \u0026#34;Exeggutor\u0026#34;, \u0026#34;Cubone\u0026#34;, \u0026#34;Marowak\u0026#34;, \u0026#34;Hitmonlee\u0026#34;, \u0026#34;Hitmonchan\u0026#34;, \u0026#34;Lickitung\u0026#34;, \u0026#34;Koffing\u0026#34;, \u0026#34;Weezing\u0026#34;, \u0026#34;Rhyhorn\u0026#34;, \u0026#34;Rhydon\u0026#34;, \u0026#34;Chansey\u0026#34;, \u0026#34;Tangela\u0026#34;, \u0026#34;Kangaskhan\u0026#34;, \u0026#34;Horsea\u0026#34;, \u0026#34;Seadra\u0026#34;, \u0026#34;Goldeen\u0026#34;, \u0026#34;Seaking\u0026#34;, \u0026#34;Staryu\u0026#34;, \u0026#34;Starmie\u0026#34;, \u0026#34;Mr. Mime\u0026#34;, \u0026#34;Scyther\u0026#34;, \u0026#34;Jynx\u0026#34;, \u0026#34;Electabuzz\u0026#34;, \u0026#34;Magmar\u0026#34;, \u0026#34;Pinsir\u0026#34;, \u0026#34;Tauros\u0026#34;, \u0026#34;Magikarp\u0026#34;, \u0026#34;Gyarados\u0026#34;, \u0026#34;Lapras\u0026#34;, \u0026#34;Ditto\u0026#34;, \u0026#34;Eevee\u0026#34;, \u0026#34;Vaporeon\u0026#34;, \u0026#34;Jolteon\u0026#34;, \u0026#34;Flareon\u0026#34;, \u0026#34;Porygon\u0026#34;, \u0026#34;Omanyte\u0026#34;, \u0026#34;Omastar\u0026#34;, \u0026#34;Kabuto\u0026#34;, \u0026#34;Kabutops\u0026#34;, \u0026#34;Aerodactyl\u0026#34;, \u0026#34;Snorlax\u0026#34;, \u0026#34;Articuno\u0026#34;, \u0026#34;Zapdos\u0026#34;, \u0026#34;Moltres\u0026#34;, \u0026#34;Dratini\u0026#34;, \u0026#34;Dragonair\u0026#34;, \u0026#34;Dragonite\u0026#34;, \u0026#34;Mewtwo\u0026#34;, \u0026#34;Mew\u0026#34; ] } override func viewDidLoad() { super.viewDidLoad() setupView() setupCollectionView() setupDataSource() updateSnapshot(with: mockData) // call the update and pass the data } // more code ... Pass the data to the snapshot and run the code, you should see your data in the collection.\nIt should looks and behaves like the video above.\nConclusion Congratulations! You’ve successfully set up a UICollectionView using a diffable data source and a custom cell, all programmatically. By following this tutorial, you now understand the basics of UICollectionView and how to leverage diffable data sources to manage your data efficiently. This approach not only simplifies your code but also ensures smooth and animated updates to your collection view.\nIn this post, we’ve covered:\nCreating a minimal view controller and adding a UICollectionView to it. Setting up a layout using UICollectionViewFlowLayout. Understanding the importance of a layout object for UICollectionView. Implementing a diffable data source to manage and update your collection view data. Creating and registering a custom cell to display text within your collection view. With these foundational concepts in place, you’re well-equipped to build more complex and dynamic collection views in your iOS apps. Whether you’re displaying a grid of images, a list of text items, or something entirely custom, UICollectionView and diffable data sources provide the flexibility and power you need.\nStay tuned for more in our UIKit Learning series, where we’ll dive deeper into advanced topics and best practices. Happy coding!\nApendix Here is the full code.\nimport UIKit class ViewController: UIViewController { private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource\u0026lt;Int, String\u0026gt;! private var mockData: [String] { [ \u0026#34;Bulbasaur\u0026#34;, \u0026#34;Ivysaur\u0026#34;, \u0026#34;Venusaur\u0026#34;, \u0026#34;Charmander\u0026#34;, \u0026#34;Charmeleon\u0026#34;, \u0026#34;Charizard\u0026#34;, \u0026#34;Squirtle\u0026#34;, \u0026#34;Wartortle\u0026#34;, \u0026#34;Blastoise\u0026#34;, \u0026#34;Caterpie\u0026#34;, \u0026#34;Metapod\u0026#34;, \u0026#34;Butterfree\u0026#34;, \u0026#34;Weedle\u0026#34;, \u0026#34;Kakuna\u0026#34;, \u0026#34;Beedrill\u0026#34;, \u0026#34;Pidgey\u0026#34;, \u0026#34;Pidgeotto\u0026#34;, \u0026#34;Pidgeot\u0026#34;, \u0026#34;Rattata\u0026#34;, \u0026#34;Raticate\u0026#34;, \u0026#34;Spearow\u0026#34;, \u0026#34;Fearow\u0026#34;, \u0026#34;Ekans\u0026#34;, \u0026#34;Arbok\u0026#34;, \u0026#34;Pikachu\u0026#34;, \u0026#34;Raichu\u0026#34;, \u0026#34;Sandshrew\u0026#34;, \u0026#34;Sandslash\u0026#34;, \u0026#34;Nidoran♀\u0026#34;, \u0026#34;Nidorina\u0026#34;, \u0026#34;Nidoqueen\u0026#34;, \u0026#34;Nidoran♂\u0026#34;, \u0026#34;Nidorino\u0026#34;, \u0026#34;Nidoking\u0026#34;, \u0026#34;Clefairy\u0026#34;, \u0026#34;Clefable\u0026#34;, \u0026#34;Vulpix\u0026#34;, \u0026#34;Ninetales\u0026#34;, \u0026#34;Jigglypuff\u0026#34;, \u0026#34;Wigglytuff\u0026#34;, \u0026#34;Zubat\u0026#34;, \u0026#34;Golbat\u0026#34;, \u0026#34;Oddish\u0026#34;, \u0026#34;Gloom\u0026#34;, \u0026#34;Vileplume\u0026#34;, \u0026#34;Paras\u0026#34;, \u0026#34;Parasect\u0026#34;, \u0026#34;Venonat\u0026#34;, \u0026#34;Venomoth\u0026#34;, \u0026#34;Diglett\u0026#34;, \u0026#34;Dugtrio\u0026#34;, \u0026#34;Meowth\u0026#34;, \u0026#34;Persian\u0026#34;, \u0026#34;Psyduck\u0026#34;, \u0026#34;Golduck\u0026#34;, \u0026#34;Mankey\u0026#34;, \u0026#34;Primeape\u0026#34;, \u0026#34;Growlithe\u0026#34;, \u0026#34;Arcanine\u0026#34;, \u0026#34;Poliwag\u0026#34;, \u0026#34;Poliwhirl\u0026#34;, \u0026#34;Poliwrath\u0026#34;, \u0026#34;Abra\u0026#34;, \u0026#34;Kadabra\u0026#34;, \u0026#34;Alakazam\u0026#34;, \u0026#34;Machop\u0026#34;, \u0026#34;Machoke\u0026#34;, \u0026#34;Machamp\u0026#34;, \u0026#34;Bellsprout\u0026#34;, \u0026#34;Weepinbell\u0026#34;, \u0026#34;Victreebel\u0026#34;, \u0026#34;Tentacool\u0026#34;, \u0026#34;Tentacruel\u0026#34;, \u0026#34;Geodude\u0026#34;, \u0026#34;Graveler\u0026#34;, \u0026#34;Golem\u0026#34;, \u0026#34;Ponyta\u0026#34;, \u0026#34;Rapidash\u0026#34;, \u0026#34;Slowpoke\u0026#34;, \u0026#34;Slowbro\u0026#34;, \u0026#34;Magnemite\u0026#34;, \u0026#34;Magneton\u0026#34;, \u0026#34;Farfetch\u0026#39;d\u0026#34;, \u0026#34;Doduo\u0026#34;, \u0026#34;Dodrio\u0026#34;, \u0026#34;Seel\u0026#34;, \u0026#34;Dewgong\u0026#34;, \u0026#34;Grimer\u0026#34;, \u0026#34;Muk\u0026#34;, \u0026#34;Shellder\u0026#34;, \u0026#34;Cloyster\u0026#34;, \u0026#34;Gastly\u0026#34;, \u0026#34;Haunter\u0026#34;, \u0026#34;Gengar\u0026#34;, \u0026#34;Onix\u0026#34;, \u0026#34;Drowzee\u0026#34;, \u0026#34;Hypno\u0026#34;, \u0026#34;Krabby\u0026#34;, \u0026#34;Kingler\u0026#34;, \u0026#34;Voltorb\u0026#34;, \u0026#34;Electrode\u0026#34;, \u0026#34;Exeggcute\u0026#34;, \u0026#34;Exeggutor\u0026#34;, \u0026#34;Cubone\u0026#34;, \u0026#34;Marowak\u0026#34;, \u0026#34;Hitmonlee\u0026#34;, \u0026#34;Hitmonchan\u0026#34;, \u0026#34;Lickitung\u0026#34;, \u0026#34;Koffing\u0026#34;, \u0026#34;Weezing\u0026#34;, \u0026#34;Rhyhorn\u0026#34;, \u0026#34;Rhydon\u0026#34;, \u0026#34;Chansey\u0026#34;, \u0026#34;Tangela\u0026#34;, \u0026#34;Kangaskhan\u0026#34;, \u0026#34;Horsea\u0026#34;, \u0026#34;Seadra\u0026#34;, \u0026#34;Goldeen\u0026#34;, \u0026#34;Seaking\u0026#34;, \u0026#34;Staryu\u0026#34;, \u0026#34;Starmie\u0026#34;, \u0026#34;Mr. Mime\u0026#34;, \u0026#34;Scyther\u0026#34;, \u0026#34;Jynx\u0026#34;, \u0026#34;Electabuzz\u0026#34;, \u0026#34;Magmar\u0026#34;, \u0026#34;Pinsir\u0026#34;, \u0026#34;Tauros\u0026#34;, \u0026#34;Magikarp\u0026#34;, \u0026#34;Gyarados\u0026#34;, \u0026#34;Lapras\u0026#34;, \u0026#34;Ditto\u0026#34;, \u0026#34;Eevee\u0026#34;, \u0026#34;Vaporeon\u0026#34;, \u0026#34;Jolteon\u0026#34;, \u0026#34;Flareon\u0026#34;, \u0026#34;Porygon\u0026#34;, \u0026#34;Omanyte\u0026#34;, \u0026#34;Omastar\u0026#34;, \u0026#34;Kabuto\u0026#34;, \u0026#34;Kabutops\u0026#34;, \u0026#34;Aerodactyl\u0026#34;, \u0026#34;Snorlax\u0026#34;, \u0026#34;Articuno\u0026#34;, \u0026#34;Zapdos\u0026#34;, \u0026#34;Moltres\u0026#34;, \u0026#34;Dratini\u0026#34;, \u0026#34;Dragonair\u0026#34;, \u0026#34;Dragonite\u0026#34;, \u0026#34;Mewtwo\u0026#34;, \u0026#34;Mew\u0026#34; ] } override func viewDidLoad() { super.viewDidLoad() setupView() setupCollectionView() setupDataSource() updateSnapshot(with: mockData) } } private extension ViewController { func setupView() { view.backgroundColor = .systemBackground } func setupCollectionView() { // Creating layout let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 100, height: 100) layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) layout.scrollDirection = .horizontal // creating actual collection collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .darkGray // adding to view view.addSubview(collectionView) // adding constraints collectionView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor) ]) } func setupDataSource() { // registering cell to the collection collectionView.register(CustomCollectionCell.self, forCellWithReuseIdentifier: CustomCollectionCell.name) // creating and configuring diffable data source dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, name in guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomCollectionCell.name, for: indexPath) as? CustomCollectionCell else { fatalError(\u0026#34;Unable to dequeue CustomCollectionCell\u0026#34;) } cell.backgroundColor = .orange cell.update(with: name) return cell } } // updating the diffable with new data func updateSnapshot(with data: [String]) { var snapshot = NSDiffableDataSourceSnapshot\u0026lt;Int, String\u0026gt;() snapshot.appendSections([0]) snapshot.appendItems(data) // Adding more items dataSource.apply(snapshot, animatingDifferences: true) } } final class CustomCollectionCell: UICollectionViewCell { static let name = \u0026#34;CustomCollectionCell\u0026#34; private var label: UILabel! override init(frame: CGRect) { super.init(frame: frame) setupLabel() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } func update(with data: String) { label.text = data } private func setupLabel() { label = UILabel() label.textAlignment = .center self.contentView.addSubview(label) // remember to add it to the contentView label.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ label.topAnchor.constraint(equalTo: contentView.topAnchor), // we are referring to contentView not view label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), // we are referring to contentView not view label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), // we are referring to contentView not view label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), // we are referring to contentView not view ]) } } ","date":"2024-06-09T12:17:07Z","permalink":"/p/working-with-collections/","title":"Working With Collections"},{"content":"In some projects, you might not want to use Storyboards or XIBs for various reasons, such as greater flexibility and control over your views, better version control, or simply personal preference. If that’s your case, this post will guide you through the basic steps to configure a Storyboard project to work with UIKit programmatically.\nStep-by-Step Guide I\u0026rsquo;m using Xcode 15.4, the graphical user interface (GUI) may change in the future causing some images to be outdated or some steps to be different.\n1. Create a New Project Start by creating a new iOS project in Xcode:\nOpen Xcode and select \u0026ldquo;Create a new Xcode project\u0026rdquo;. Choose the \u0026ldquo;App\u0026rdquo; template under the iOS tab. Enter your project details (Product Name, Team, etc.) and make sure \u0026ldquo;Storyboard\u0026rdquo; is selected for the User Interface. Save your project wherever you like and optionally tick the \u0026ldquo;Create Git repository on my Mac\u0026rdquo; to initialize a local git repository for this project. 2. Remove the Main Storyboard Next, we\u0026rsquo;ll remove the storyboard file and configure the project to work without it:\nDelete Main.storyboard: In the Project Navigator, locate Main.storyboard. Right-click on Main.storyboard and select \u0026ldquo;Delete\u0026rdquo;. Choose \u0026ldquo;Move to Trash\u0026rdquo; to remove it completely. Remove Storyboard References from Info.plist: Open Info.plist. Delete the Main storyboard file base name key (usually named Storyboard Name). Remove Storyboard References from Build Settings: Select your project in the Project Navigator. Select your app target. Go to the \u0026ldquo;Build Settings\u0026rdquo; tab. Search for Main and make sure there are no references to Main. 3. Set the Root View Controller Programmatically Depending on whether your project uses a SceneDelegate (iOS 13 and later) or not, you\u0026rsquo;ll set the root view controller differently.\nUsing AppDelegate (iOS 12 and earlier or single-window apps) Open AppDelegate.swift.\nIn the application(_:didFinishLaunchingWithOptions:) method, add the following code to set up the window and root view controller:\nimport UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u0026gt; Bool { window = UIWindow(frame: UIScreen.main.bounds) let initialViewController = YourInitialViewController() window?.rootViewController = UINavigationController(rootViewController: initialViewController) window?.makeKeyAndVisible() return true } } Using SceneDelegate (iOS 13 and later) Open SceneDelegate.swift.\nIn the scene(_:willConnectTo:options:) method, add the following code to set up the window and root view controller:\nimport UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) let initialViewController = YourInitialViewController() window?.rootViewController = UINavigationController(rootViewController: initialViewController) window?.makeKeyAndVisible() } } In these files, you can do more complex setups like configuring routers for navigation, setting up a tab bar controller, or managing a split view controller, depending on your app\u0026rsquo;s needs.\n4. Create Your Initial View Controller Create a new Swift file for your initial view controller:\nIn the Project Navigator, right-click on the project folder and select \u0026ldquo;New File\u0026rdquo;.\nChoose \u0026ldquo;Swift File\u0026rdquo; and name it YourInitialViewController.swift.\nImplement your view controller as follows:\nimport UIKit class YourInitialViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Set the background color to visually verify the setup view.backgroundColor = .white // Additional setup for your view controller } } 5. Run the Project Build and run your project. You should see a white screen, indicating that your initial view controller is being presented programmatically.\nConclusion By following these steps, you’ve successfully configured your Storyboard-based project to work programmatically with UIKit. This setup provides you with greater flexibility and control over your views, and you can now add your UI elements directly in code.\nHappy coding!\n","date":"2024-05-26T22:35:31Z","permalink":"/p/configuring-a-programmatic-uikit-project/","title":"Configuring a Programmatic UIKit Project"},{"content":"Tabs bars are one the most recognizable UI elements for an iOS apps. We can find this element in build in apps like Phone, Photos, Music, AppStore, and many more.\nLucky for us, this element is quite easy to use, and in this tutorial, we\u0026rsquo;ll learn how to create a tab bar programmatically using UIKit. Let\u0026rsquo;s start!\nSimple example Let\u0026rsquo;s start by configuring our project to allow us to instantiate our views programmatically. You can follow this guide.\n1. Create the Tab Bar class I prefer to have a separate class that handle all the Tab Bar related responsibilities, so let\u0026rsquo;s create a new file called TabBarViewController.swift and add the following code.\nimport UIKit final class TabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() } } This is the minimum code we need to have a working tab bar.\n2. Adding the tab bar to the app Now we can instantiate this as we would do with a UINavigationController.\nOpen SceneDelegate.swift and and update the willConnectTo method as follows: func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) window?.rootViewController = TabBarController() window?.makeKeyAndVisible() } We are assigning the new Tab Bar as the window root view controller. This will present this as the first element in our screen.\nNow run the app, you should see an empty tab bar in the screen.\n3. Creating the View Controllers to show We need to have some view controllers to show on the screen. Let\u0026rsquo;s make two simple dummy ones.\nCreate two new files, AViewController.swift and BViewController.swift and add the following code:\n// A View Controller import UIKit final class AViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellow } } // B View Controller import UIKit final class BViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .orange } } These two view controllers are quite simple, with the only difference being their background color.\n4. Presenting the View Controllers in the Tab Bar Each UIViewController has a property called tabBarItem to which we can assign the tab bar item it will display in a tab bar.\nOpen the TabBarController.swift and update the code as follows:\nimport UIKit enum TabBarTag: Int { case a, b } final class TabBarController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() setupTabBar() // 3. Call the new function } } // 1 .Add this private extension private extension TabBarController { // 2. Add this function that will instantiate the ViewControllers and assign their tab bar items func setupTabBar() { let aViewController = AViewController() // Adding the tab bar item to show in the tab bar for AViewController aViewController.tabBarItem = UITabBarItem( title: \u0026#34;A\u0026#34;, image: UIImage(systemName: \u0026#34;1.lane\u0026#34;), tag: TabBarTag.a.hashValue ) let bViewController = BViewController() // Adding the tab bar item to show in the tab bar for BViewController bViewController.tabBarItem = UITabBarItem( title: \u0026#34;B\u0026#34;, image: UIImage(systemName: \u0026#34;2.lane\u0026#34;), tag: TabBarTag.b.hashValue ) // UITabBarController contains an array of the view controllers that will be showing in the screen self.viewControllers = [aViewController, bViewController] } } I’m using an enum for the tag, so that we can refer to each tab bar item easily in the future for other parts of our app.\n5. Run the app Now run the app and see the tab bar in action.\nWith this little code, we have a simple tab bar implemented in our app.\nAdvanced Usage Under development \u0026hellip;\n","date":"2024-05-26T22:17:06Z","permalink":"/p/programmatic-tab-bar/","title":"Programmatic Tab Bar"},{"content":"VSCode + Dev Containers are a great way to create custom environments without modifying our actual system.\nThey provide a consistent development environment, ensure dependencies are managed correctly, and make it easy to share setups with your team.\nHere are some configurations you can use to create them.\nVapor { \u0026#34;name\u0026#34;: \u0026#34;Swift\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;swift:latest\u0026#34;, \u0026#34;features\u0026#34;: { \u0026#34;ghcr.io/devcontainers/features/common-utils:2\u0026#34;: { \u0026#34;installZsh\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;username\u0026#34;: \u0026#34;vscode\u0026#34;, \u0026#34;userUid\u0026#34;: \u0026#34;1000\u0026#34;, \u0026#34;userGid\u0026#34;: \u0026#34;1000\u0026#34;, \u0026#34;upgradePackages\u0026#34;: \u0026#34;false\u0026#34; }, \u0026#34;ghcr.io/devcontainers/features/git:1\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;os-provided\u0026#34;, \u0026#34;ppa\u0026#34;: \u0026#34;false\u0026#34; } }, \u0026#34;runArgs\u0026#34;: [ \u0026#34;--cap-add=SYS_PTRACE\u0026#34;, \u0026#34;--security-opt\u0026#34;, \u0026#34;seccomp=unconfined\u0026#34; ], \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;settings\u0026#34;: { \u0026#34;lldb.library\u0026#34;: \u0026#34;/usr/lib/liblldb.so\u0026#34; }, \u0026#34;extensions\u0026#34;: [ \u0026#34;sswg.swift-lang\u0026#34;, \u0026#34;streetsidesoftware.code-spell-checker\u0026#34; ] } }, \u0026#34;remoteUser\u0026#34;: \u0026#34;root\u0026#34;, \u0026#34;postCreateCommand\u0026#34;: \u0026#34;apt-get update \u0026amp;\u0026amp; apt-get install -y libssl-dev libsqlite3-dev zlib1g-dev make \u0026amp;\u0026amp; rm -rf toolbox \u0026amp;\u0026amp; git clone https://github.com/vapor/toolbox.git \u0026amp;\u0026amp; cd toolbox \u0026amp;\u0026amp; make install \u0026amp;\u0026amp; cd .. \u0026amp;\u0026amp; rm -rf toolbox\u0026#34; } Scala { \u0026#34;name\u0026#34;: \u0026#34;Scala\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;mcr.microsoft.com/devcontainers/java:1-21-bullseye\u0026#34;, \u0026#34;features\u0026#34;: { \u0026#34;ghcr.io/devcontainers/features/java:1\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;none\u0026#34;, \u0026#34;installMaven\u0026#34;: \u0026#34;false\u0026#34;, \u0026#34;installGradle\u0026#34;: \u0026#34;false\u0026#34; } }, \u0026#34;postCreateCommand\u0026#34;: \u0026#34;curl -fL https://github.com/VirtusLab/coursier-m1/releases/latest/download/cs-aarch64-pc-linux.gz | gzip -d \u0026gt; cs \u0026amp;\u0026amp; chmod +x cs \u0026amp;\u0026amp; yes | ./cs setup \u0026amp;\u0026amp; echo \u0026#39;before start run source ~/.profile\u0026#39;\u0026#34;, \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;extensions\u0026#34;: [ \u0026#34;scala-lang.scala\u0026#34;, \u0026#34;streetsidesoftware.code-spell-checker\u0026#34; ] } } } ","date":"2024-05-21T00:46:23Z","permalink":"/p/vscode-dev-container-configurations/","title":"VSCode Dev Container Configurations"},{"content":"Lately, I\u0026rsquo;ve been writing a lot about UIKit and I noticed I haven\u0026rsquo;t covered the delegate pattern.\nThis pattern is not exclusive to UIKit but it\u0026rsquo;s quite used in this framework.\nIn this post, we\u0026rsquo;ll dive into the Delegation Pattern, a fundamental design pattern in iOS development that helps in creating a well-structured codebase.\nWhat is the Delegation Pattern? The Delegation Pattern is a design pattern where one object (the delegator) hands off (or delegates) some of its responsibilities to another object (the delegate). This allows for a clear separation of concerns and makes it easier to manage and extend code.\nBy responsibilities, we mean functions or actions the object needs to perform but doesn\u0026rsquo;t want to implement itself because it\u0026rsquo;s not directly responsible. This allows for a clear separation of concerns and makes it easier to manage and extend code.\nWhy Use the Delegation Pattern? Separation of Concerns: It helps in dividing the functionality into different classes, making the code more modular and easier to manage. Reusability: Delegate objects can be reused across different contexts and components. Flexibility: It provides a way to customize or extend the behavior of a class without modifying its source code. How to Implement the Delegation Pattern in Swift Let\u0026rsquo;s go through a practical example to illustrate the Delegation Pattern.\nStep 1: Define the Protocol First, define a protocol that outlines the responsibilities to be delegated.\nprotocol CustomDelegate: AnyObject { func someDelegatedFunction() func someOtherDelegatedFunction(withString: String) } Here, we are specifying the actions our object needs to perform but are not implemented by the object itself.\nWe can also pass information back to the delegate object. This can be as simple as one string or as complex as the whole object.\nStep 2: Create the Delegator Next, create the delegator class, which will hold a reference to the delegate and call its methods when appropriate.\nclass SomeObject { weak var delegate: CustomDelegate? func someAction() { delegate?.someDelegatedFunction() } func someOtherAction() { delegate?.someOtherDelegatedFunction(withString: \u0026#34;Some data being passed\u0026#34;) } } It\u0026rsquo;s important to mark the delegate property as weak to avoid retain cycles and memory leaks.\nThis is a minimum example. In most cases, the object will also contain its own logic and only some parts will be delegated.\nStep 3: Implement the Delegate Finally, implement the delegate in a class that will handle the delegated tasks.\nclass OtherClassThatWantsToPerformTheActions { // Creating the object that needs the delegate // This can also be injected from a different object if we don\u0026#39;t want to create it here. let someObject = SomeObject() init() { // Assigning this class as the delegate object, // now someObject can call the implemented methods in this class when needed. someObject.delegate = self } } // Implementing the delegate methods needed to be a valid delegate extension OtherClassThatWantsToPerformTheActions: CustomDelegate { func someDelegatedFunction() { // Logic implemented when the method is called by the delegator } func someOtherDelegatedFunction(withString: String) { // Logic implemented when the method is called by the delegator, // In this case, the delegator is passing back some information we may need to perform the action. } } Here are two crucial steps: we need to assign to SomeObject the class that will handle the delegate, and we need to implement the delegate protocol in the delegate class so that the assigned object can respond to the actions of SomeObject.\nYou\u0026rsquo;re done! Now, when you need to perform a delegated action, the responsible object will be called and perform the action in place of our delegator.\nExample with UITableView Let\u0026rsquo;s see an example involving UITableView, which frequently uses the delegation pattern.\nWith objects managed by UIKit we only need to implement the step 3 of the previous example (for our custom objects we need to implement all steps ourselves).\nWe will make our ViewController to act as the delegate for the UITableView.\nimport UIKit class ViewController: UIViewController { // Creating the TableView that needs the delegate for certain actions let tableView = UITableView() override func viewDidLoad() { super.viewDidLoad() // Set up the table view tableView.frame = view.bounds tableView.delegate = self // assigning this object as the delegate tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: \u0026#34;cell\u0026#34;) view.addSubview(tableView) } } // MARK: - UITableViewDataSource extension ViewController: UITableViewDataSource { // Return the number of rows in the section func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u0026gt; Int { return 10 // Example number of rows } // Configure and return the cell for the given index path func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u0026gt; UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: \u0026#34;cell\u0026#34;, for: indexPath) cell.textLabel?.text = \u0026#34;Row \\(indexPath.row)\u0026#34; return cell } } // MARK: - UITableViewDelegate extension ViewController: UITableViewDelegate { // Handle row selection // this method is defined in the delegate protocol for UITableView func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { print(\u0026#34;Selected row \\(indexPath.row)\u0026#34;) tableView.deselectRow(at: indexPath, animated: true) } } Both the delegate and the data source follow the delegation pattern, but the delegate manages user interactions and appearance, while the data source provides the data to be displayed.\nIn this example:\nDefine the Protocols: We use the built-in UITableViewDelegate and UITableViewDataSource protocols.\nCreate the Delegator: The UITableView instance is created and set up in the viewDidLoad method. It is configured to use the ViewController as its delegate and data source.\nImplement the Delegate and Data Source: The ViewController class conforms to the UITableViewDelegate and UITableViewDataSource protocols. It implements required methods such as tableView(_:numberOfRowsInSection:) and tableView(_:cellForRowAt:) for the data source, and tableView(_:didSelectRowAt:) for the delegate.\nConclusion The Delegation Pattern is a fundamental design pattern in iOS development that allows for a clear separation of concerns, making your code more modular and easier to manage. By delegating responsibilities, you can create reusable and flexible components that improve the maintainability of your codebase.\nIn this post, we explored the Delegation Pattern, learned how to implement it with a simple custom example, and saw how it is applied in a real-world scenario with UITableView.\nUnderstanding and utilizing this pattern will help you create well-structured and efficient code, ensuring that your iOS applications remain robust and scalable. As always, following best practices and leveraging powerful design patterns like delegation will lead to better, more maintainable applications.\nHappy coding!\n","date":"2024-05-16T22:16:34Z","permalink":"/p/delegate-pattern-in-swift/","title":"Delegate Pattern in Swift"},{"content":"One common issue with programmatic UIKit development is dealing with the creation and configuration of UI objects. Configuring UI elements in our code can be cumbersome and hard to synchronize their behavior and style across several screens.\nBy using the Builder Pattern, we can centralize the creation and configuration of these objects. This simplifies our codebase and decouples the creation of UI objects from our views.\nWhat is the Builder Pattern? The Builder Pattern is a creational design pattern that allows us to construct complex objects step by step. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.\nI don\u0026rsquo;t want to focus on the formal definition too much (you can find a lot of great articles on the topic like this one). Instead, I prefer to show you a working example, so let\u0026rsquo;s go for it.\nPractical Example Let\u0026rsquo;s start with the builder. In this example, I want a way to configure a button with some defaults, so I can rely on these defaults to simplify further and accept modifications for specific cases.\nApplying the Builder Pattern to UIKit Let\u0026rsquo;s see how we can use the Builder Pattern to create and configure a UIButton in UIKit.\nIn a Storyboard project let\u0026rsquo;s create a new file ButtonBuilder.swift and add the following code.\nclass ButtonBuilder { private var button = UIButton(type: .system) func setTamic(_ active: Bool) -\u0026gt; Self { button.translatesAutoresizingMaskIntoConstraints = active return self } func setTitle(_ title: String) -\u0026gt; Self { button.setTitle(title, for: .normal) return self } func setTitleColor(_ color: UIColor) -\u0026gt; Self { button.setTitleColor(color, for: .normal) return self } func setBackgroundColor(_ color: UIColor) -\u0026gt; Self { button.backgroundColor = color return self } func setCornerRadius(_ radius: CGFloat) -\u0026gt; Self { button.layer.cornerRadius = radius return self } func setAction(_ action: @escaping () -\u0026gt; Void) -\u0026gt; Self { button.addAction(UIAction {_ in action() }, for: .touchUpInside) return self } func build() -\u0026gt; UIButton { return button } } They key point to implement a builder is that we have configuration functions that return the builder itself. These functions can be chained with other configuration functions as needed. Once we are done with the configuration, we call the build function to return the actual object configured with the selected options.\nUsing the builder inside a ViewController To use our builder let\u0026rsquo;s create a new file called TestViewController.swift and add the following code.\nimport UIKit class ExampleViewController: UIViewController { private lazy var button = ButtonBuilder() .setTamic(false) .setTitle(\u0026#34;Example\u0026#34;) .setAction { print(\u0026#34;Button pressed\u0026#34;) } .setBackgroundColor(.cyan) .build() override func viewDidLoad() { view.addSubview(button) NSLayoutConstraint.activate([ button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } } #Preview { ExampleVC() } The final result resembles the simple and declarative style of SwiftUI!\nRemember to use [weak self] if you\u0026rsquo;re capturing self or any property of self in your button action.\nTest the button action in the Canvas and enjoy!\nConsiderations Sometimes the object cannot be created before we have all the properties or dependencies. In these cases, we can gather the dependencies or properties and only create the object in the build function, where we inject the desired properties.\nHere is an example for a Label to demonstrate this.\nclass LabelBuilder { private var text: String = \u0026#34;\u0026#34; private var numberOfLines: Int = 0 private var tamic: Bool = false func withText(_ text: String) -\u0026gt; Self { self.text = text return self } func withNumberOfLines(_ number: Int) -\u0026gt; Self { self.numberOfLines = number return self } func withAutoLayout(_ tamic: Bool) -\u0026gt; Self { self.tamic = tamic return self } func build() -\u0026gt; UILabel { let label = UILabel() label.text = text label.numberOfLines = numberOfLines label.translatesAutoresizingMaskIntoConstraints = tamic return label } } In this example, we define the properties beforehand and assign default values that make sense for our final object. Changes to these defaults are optional and can be made using the configuration methods.\nConclusion By leveraging the Builder Pattern, we can streamline the process of creating and configuring UI elements in UIKit. This approach not only makes our codebase more manageable but also decouples the configuration from the views, enhancing maintainability and reusability. Whether you are setting up a simple button or a more complex object, the Builder Pattern provides a flexible and clean solution.\nI hope this guide helps you simplify your UIKit development. Give it a try in your next project and see the difference it can make!\n","date":"2024-05-15T20:52:02Z","permalink":"/p/builder-pattern-to-simplify-uikit-object-creation/","title":"Builder Pattern to Simplify UIKit Object Creation"},{"content":"One of the standout features of SwiftUI is its reactive paradigm, which seamlessly updates views as data changes. This powerful approach isn\u0026rsquo;t confined to SwiftUI alone; it can also be harnessed in UIKit through the use of the Combine framework, and starting with iOS 17, through the new Observation framework.\nIn this post, we\u0026rsquo;re going to explore using MVVM with UIKit, leveraging Combine to achieve reactive behaviors similar to SwiftUI. I hope you find this information useful and that it helps you improve the way you create and think about your applications. Join me as we dive into making your UIKit apps more responsive and robust.\nWhat is MVVM? MVVM (Model-View-ViewModel) is a popular architectural pattern that divides the application into three interconnected components. This separation aids in managing complexities and enhances the maintainability of the application.\nThe Model includes both the data and the business logic specific to the application. It represents the actual data content and the operations that can manipulate this data. The ViewModel acts as an intermediary between the Model and the View. It handles most of the view logic, transforming data from the Model in a way that the View can easily present. The ViewModel also responds to user input and view-related logic, thus abstracting the View from the Model. A key component of the ViewModel is its reactivity; it notifies the View of changes to the data automatically, facilitating a dynamic user experience. The View is responsible for presenting the data provided by the ViewModel in a user-friendly format. It focuses purely on the visual representation, relying on the ViewModel to manage any state or processing of data. The View interacts with the ViewModel through mechanisms like data binding, which ensures that changes in the ViewModel are automatically reflected in the View. By separating concerns in this manner, MVVM facilitates a clear division of labor with respect to how data is managed, presented, and interacted with. This not only makes the application easier to maintain but also enhances its scalability and testability.\nHow to Obtain Reactivity? To ensure that view controllers are aware of changes in the ViewModels, a mechanism for subscribing to and receiving notifications of these changes is necessary. One effective approach to achieve this in iOS development is through the use of Apple\u0026rsquo;s Combine framework.\nCombine allows ViewModels to publish updates whenever their state changes. View controllers can subscribe to these publications and react accordingly. This setup ensures that the View layer remains responsive and up-to-date with the latest data. Here’s a basic example of how this might be implemented:\nViewModel publishes changes: The ViewModel includes properties marked with @Published, triggering the publisher whenever the property\u0026rsquo;s value changes. View controller subscribes to changes: In the view controller, a subscriber is set up to listen to the ViewModel\u0026rsquo;s publishers. Using Combine\u0026rsquo;s sink(receiveValue:) method, the view controller can update its UI components whenever it receives new data. This model promotes a reactive and efficient architecture where data flow and user interface updates are tightly coordinated.\nMaking an app: MVVM Counter Let\u0026rsquo;s recreate a simple counter app using MVVM + Combine + UIKit. This app is a great first introduction to reactive programming since it shows the basic components and their interactions.\nWhere is the model? This app is quite simple, the model is just an Int that will be updated every time we press the button. That\u0026rsquo;s why we aren\u0026rsquo;t creating an explicit model object. But remember with more complex models is a good idea to have a separate struct for them.\nCreating the View Model The View Model will contains the state and the methods to interact with the states, this object will relay on Combine to publish the changes to their subscribers.\nCreate a new file called ViewModel.swift and add the following code.\nimport Combine class ViewModel: ObservableObject { @Published private(set) var counter = 0 func addOne() { counter += 1 } } Here is a brief description of the code:\nclass ViewModel: ObservableObject: We define a class ViewModel that conforms to the ObservableObject protocol. This protocol is part of the Combine framework and allows the properties of our ViewModel to be observed by the UI for changes.\n@Published private(set) var counter = 0: Here, counter is a property that stores a numerical value, initialized to 0. The @Published attribute before counter makes it a published property, meaning any UI components observing this property can react to changes in its value. The private(set) modifier means that the value of counter can only be modified within the ViewModel class itself, enforcing encapsulation and control over how the value is changed.\nfunc addOne(): This function defines the behavior to increment the counter by 1. Whenever addOne is called, the counter property is increased, and because it\u0026rsquo;s a @Published property, all observers (such as UI components) are notified of this change.\nCreating the View To create the View we are using a UIViewController, let\u0026rsquo;s create a new file and call it ViewController.swift.\nFirst, let\u0026rsquo;s add the UI Elements to our controller. In this simple app we only need a UILabel and a UIButton.\nimport UIKit class ViewController: UIViewController { private lazy var counterLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = \u0026#34;Button pressed 0\u0026#34; return label }() private lazy var counterButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.configuration = UIButton.Configuration.filled() button.setTitle(\u0026#34;Count\u0026#34;, for: .normal) return button }() override func viewDidLoad() { super.viewDidLoad() configuration() addObjectsToView() addConstraintsToObjects() } } private extension ViewController { func configuration() { view.backgroundColor = .systemBackground counterButton.addAction(UIAction { [weak self] _ in print(\u0026#34;Button tapped\u0026#34;) }, for: .touchUpInside) } func addObjectsToView() { [counterLabel, counterButton].forEach { view.addSubview($0) } } func addConstraintsToObjects() { NSLayoutConstraint.activate([ counterLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), counterLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), counterButton.topAnchor.constraint(equalTo: counterLabel.bottomAnchor, constant: 32), counterButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), counterButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32) ]) } } So far nothing new, just a simple ViewController with some UI elements.\nAdding Reactivity Now comes the interesting part of the MVVM pattern, reacting to changes in the ViewModel.\nThe first step it to include Combine in the ViewController.\nNext, let add a dependency to the ViewModel so that our ViewController can subscribe to the changes published by the view model.\nInside the ViewController class add the following properties\nprivate let viewModel = ViewModel() private var cancellables: Set\u0026lt;AnyCancellable\u0026gt; = [] The above code may become confusing because of the cancellables property, this is used to avoid memory leaks.\nWhen you use the Combine framework to handle data flows in your applications, you often set up subscriptions where your UI components or other parts of your app listen to changes in your data. These subscriptions create what are known as \u0026ldquo;cancellables,\u0026rdquo; which are objects that represent the life of a subscription. To manage these effectively and avoid memory leaks, we use AnyCancellable.\nBy adding these AnyCancellable instances to a set called cancellables, you keep a reference to each active subscription. Storing them in a set is convenient because it ensures that each subscription is kept alive as long as you need it and is automatically cancelled and removed from memory when no longer needed or when the containing object (like a view model or view controller) is deallocated.\nNext we need to subscribe to the published property of the view model.\nGo to the configuration function and add the following at the bottom.\nviewModel.$counter .sink { [weak self] value in self?.counterLabel.text = \u0026#34;Button Pressed \\(value)\u0026#34; }.store(in: \u0026amp;cancellables) The code snippet demonstrates how to update a user interface component in response to changes in a ViewModel property using Combine. Here’s how it works:\n**viewModel.$counter**: This line begins observing changes to the counter property in the ViewModel. The $ prefix is a shorthand in Combine that provides a publisher for the property, which emits a new value each time the property changes.\n.sink: The sink method is used to subscribe to the publisher and handle new data as it arrives. This method takes a closure that defines what to do with the emitted values. In this case, whenever counter updates, the closure receives the new counter value.\n[weak self] value in: The closure captures self weakly to prevent retain cycles, which can lead to memory leaks. value is the latest value of counter emitted by the publisher.\nself?.counterLabel.text = \u0026ldquo;Button Pressed (value)\u0026rdquo;: Inside the closure, the text of counterLabel (a UI label) is updated to display the number of times a button has been pressed, as indicated by the counter value.\n.store(in: \u0026amp;cancellables): This part of the code is crucial for managing the subscription\u0026rsquo;s lifecycle. It adds the subscription to a set of cancellables (explained in the previous part about AnyCancellable). This ensures that the subscription remains active as long as needed and gets properly disposed of to prevent memory leaks.\nThis pattern is commonly used in iOS development with UIKit to dynamically update the UI in response to data changes in the ViewModel, enhancing the app\u0026rsquo;s reactivity and user experience.\nFinally let\u0026rsquo;s update the button action to call the method that will triggers the View Model updates, in the configuration method, change the button action to the following.\ncounterButton.addAction(UIAction { [weak self] _ in self?.viewModel.addOne() }, for: .touchUpInside) This will change the value in the view model each time we press the button, by changing the value we will trigger the notification in the published property, making the view controller to react to the change and update the value in the label automatically.\nHere is the final form of the ViewController.\nimport UIKit import Combine class ViewController: UIViewController { private let viewModel = ViewModel() private var cancellables: Set\u0026lt;AnyCancellable\u0026gt; = [] private lazy var counterLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.text = \u0026#34;Button pressed 0\u0026#34; return label }() private lazy var counterButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.configuration = UIButton.Configuration.filled() button.setTitle(\u0026#34;Count\u0026#34;, for: .normal) return button }() override func viewDidLoad() { super.viewDidLoad() configuration() addObjectsToView() addConstraintsToObjects() } } private extension ViewController { func configuration() { view.backgroundColor = .systemBackground counterButton.addAction(UIAction { [weak self] _ in self?.viewModel.addOne() }, for: .touchUpInside) viewModel.$counter .sink { [weak self] value in self?.counterLabel.text = \u0026#34;Button Pressed \\(value)\u0026#34; }.store(in: \u0026amp;cancellables) } func addObjectsToView() { [counterLabel, counterButton].forEach { view.addSubview($0) } } func addConstraintsToObjects() { NSLayoutConstraint.activate([ counterLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), counterLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), counterButton.topAnchor.constraint(equalTo: counterLabel.bottomAnchor, constant: 32), counterButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), counterButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32) ]) } } Running the app As simple as this you\u0026rsquo;ve implemented MVVM with Combine and UIKit. This example is a great way to observe the simplicity and power of reactive programming.\nHere is the final result.\nConclusion Throughout this post, we\u0026rsquo;ve explored how the MVVM architectural pattern can be effectively implemented in UIKit applications using the Combine framework to create reactive behaviors similar to those found in SwiftUI. By integrating MVVM with Combine, we\u0026rsquo;ve demonstrated that UIKit is far from obsolete in the era of modern, reactive iOS applications. Instead, it offers a robust platform for adopting reactive programming paradigms that can make your apps more responsive and easier to maintain.\nThe MVVM pattern not only separates concerns, enhancing the maintainability and testability of your applications, but also leverages the power of Combine to keep your UI in perfect sync with the underlying data model. As we have seen with our simple counter app example, even traditional UIKit apps can benefit immensely from the clarity and efficiency that reactive programming brings.\nI hope this walkthrough has provided you with a clear understanding and practical knowledge on integrating MVVM with UIKit using Combine, and inspires you to apply these concepts to your own iOS projects. Reactive programming might seem daunting at first, but with practice, it becomes an indispensable part of crafting elegant and efficient iOS applications.\nThank you for joining me on this journey through reactive programming with UIKit and MVVM. Happy coding!\n","date":"2024-05-13T22:55:14Z","permalink":"/p/creating-an-mvvm-counter-in-uikit/","title":"Creating an MVVM Counter In UIKit"},{"content":"In this entry, I\u0026rsquo;d like to explore the different ways we can interact with Xibs and Storyboards in our programmatic code. These files are present in many projects, so it\u0026rsquo;s important to understand the basics of how to work with them.\nLet\u0026rsquo;s start with some definitions!\nDefinitions What are Storyboards Storyboards are like a big visual map in Xcode that lets you design your app’s screens. You can drag and drop things like buttons, labels, and images onto these screens to see how they\u0026rsquo;ll look. Plus, you can easily set up how the screens connect and move between each other. It\u0026rsquo;s like drawing out your app’s flow and layout so you can get a good idea of how it\u0026rsquo;ll all work together without needing to write too much code right away.\nWhat are Xibs/Nibs Xibs, short for \u0026ldquo;XML Interface Builder,\u0026rdquo; are individual files in Xcode that help you design a specific screen or component of your app. Think of them as blueprints for smaller, reusable parts of your app\u0026rsquo;s user interface. Unlike Storyboards, which give you a big-picture view of your entire app, Xibs are focused on one screen or view at a time. You can visually arrange buttons, labels, and other UI elements in a Xib file, then load that design in your app to get a custom layout exactly how you want it. It\u0026rsquo;s a handy way to build specific UI pieces without touching too much code.\nOn the other hand, Nibs (short for \u0026ldquo;NeXT Interface Builder\u0026rdquo;) are essentially compiled versions of Xibs. When you design a user interface in Xcode using a Xib file, it gets converted into a more optimized format called a Nib file that the app can load at runtime. This makes the interface ready to use more quickly. In short, Nibs are pre-packaged user interface designs created from Xibs, enabling the app to load and use them efficiently when needed.\nSo, while you work on Xibs during development, your app uses Nibs when it runs!\nWorking with Storyboards Creating a new Storyboard When we create a new app with Storyboard, Xcode automatically will create a Main Storyboard. We can also create these files, here is how:\nInside an Xcode project let\u0026rsquo;s press CMD + N and select storyboard in the presented window. Add a name for the new Storyboard, remember to add it to the correct Group and Target. The new Storyboard is ready to use in our project, add the elements you need inside the Storyboard graphically, no code is needed here. Adding constraints to the Storyboard By default, new UI elements added to the Storyboard doesn\u0026rsquo;t know where they will be placed on the screen. To alleviate this we can add constraints to the elements in the screen.\nUI elements can be seen as rectangles on the screen and the constraints tells these sides how to relate to other elements in the screen.\nIf the UI element is affected by the language orientation you should use leading and trailing.\nOne key difference between programmatic constraints and visual constraints (those added in the Xcode Interface Builder) is that the latter are added to the closest element to the selected UI element.\nIn this example, the Label bottom is constrained to the top side of the button because that is the closest element to the Label, the other sides are related to the container view sides because this is the the closest element in relation to this UI object.\nTo add constraints using the Interface Builder we use the buttons at the bottom right.\nThese are called:\nUpdate frames Align Add new constraints Resolve Autolayout Issues We can use Add new constraints to add constrains to the selected view. Remember that this constrains will be in relation to the closest object for the selected side of the object.\nto center elements with respect to the super view, we can use Align.\nOnce these constraints are set, we can modify them by open the right panel and select Show the size inspector.\nIn this panel we can also modify the Hugging and Compression.\nHugging and Compression When working with auto layout, there are two important concepts:\nContent Hugging Priority: This controls how much a view likes to stick to its natural size. Higher priority = less likely to grow bigger than its content.\nContent Compression Resistance Priority: This controls how much a view resists shrinking below its natural size. Higher priority = less likely to be squished.\nSimply put, hugging stops a view from stretching too much, and compression resistance prevents it from getting squished too much.\nAdjusting Priorities and Values Priority Values: Default values are 250 for content hugging and 750 for compression resistance. Adjusting these values can help achieve the desired behaviors in your UI.\nIntrinsic Content Size: Some elements, like labels and buttons, have a natural size based on their content. Hugging and compression resistance help these elements maintain that size whenever possible. For example, a label with high compression resistance won\u0026rsquo;t shrink and cut off the text, while low compression resistance allows it to shrink and potentially truncate the text. A low content hugging priority allows the label to expand beyond the text\u0026rsquo;s size, while high content hugging priority keeps the label close to its content and prevents unnecessary expansion.\nHandling Ambiguity and Conflicts Ambiguous Layouts: When auto layout can\u0026rsquo;t determine the final position or size of a view, you may see a warning about ambiguous layouts. To fix this, ensure all views have enough constraints to define their positions fully.\nConflicting Constraints: Conflicting constraints happen when two or more constraints contradict each other. For instance, a button cannot be both centered and anchored to a side. Resolve these by removing or adjusting constraints to eliminate conflicts.\nUse Cases and Best Practices Adjusting Priorities: If you have a flexible view like a label next to a fixed-size view like an image, increase the label\u0026rsquo;s hugging priority to prevent unwanted expansion.\nAvoiding Common Mistakes: Be careful not to over-constrain views. Over-constraint can lead to conflicts or unexpected resizing, so it\u0026rsquo;s best to only apply necessary constraints.\nAssigning a ViewController to the Storyboard Having the storyboard is great, but we need to add code to make it function logically within our app. One way to do this is by associating a view controller with the storyboard.\nOpen the right panel in Xcode (CMD + OPT + 0) and the Storyboard file.\nSelect the View Controller Scene in the inner left panel.\nOn the right panel select Show Identity Inspector, here you will see a section called Custom Class, you add your reference in the Class section. Select any UIViewController class you want to connect to the storyboard and assign a Storyboard ID.\nAfter adding the ViewController, you will end up with this. Now you can connect your elements in the storyboard to your ViewController and access their attributes and add actions as needed.\nDon\u0026rsquo;t forget to add a Storyboard ID, we need this to reference it from objects in our code.\nLinking Storyboard elements to ViewController There are several ways of linking the UI elements in the Storyboard to a particular View Controller.\nHere is one simple approach with CONTROL + Drag.\nCONTROL + Drag means you should move your cursor (using your mouse, trackpad, or other input device) to the file on the right while you\u0026rsquo;re pressing the CONTROL key on your keyboard.\nOpen the Storyboard file. On the up right side click on Adjust Editor Options. Select Assistant Layout (CONTROL + OPTION + CMD + ENTER). CONTROL + Drag the UI elements to the code. Here is a video showing the whole process.\nFinally you can add the logic in the View Controller. For this example we have a button that changes the background color and updates the label with the selected color.\nimport UIKit class ExampleViewController: UIViewController { @IBOutlet weak var colorLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() } private func updateScreen(with color: UIColor) { view.backgroundColor = color colorLabel.text = color.accessibilityName } @IBAction func whiteAction(_ sender: Any) { updateScreen(with: .white) } @IBAction func redAction(_ sender: Any) { updateScreen(with: .red) } @IBAction func greenAction(_ sender: Any) { updateScreen(with: .green) } @IBAction func blueAction(_ sender: Any) { updateScreen(with: .blue) } } Accessing the UI element in the View Controller Interactive Elements To access interactive UI elements declared in the storyboard from the view controller, we have two options:\nUsing an IBOutlet reference. Using an associated IBAction. If we want to use the associated action, we can cast the received sender to the appropriate UI type. Here\u0026rsquo;s an example with a button.\n@IBAction func greenAction(_ sender: Any) { guard let button = sender as? UIButton else { return } print(button.titleLabel?.text ?? \u0026#34;No title label found\u0026#34;) updateScreen(with: .green) } We can do this with all UI elements that support interactivity via @IBAction.\nThe IBAction mechanism is designed specifically to handle user-triggered events, which usually means interactive controls like buttons, sliders, switches, etc.\nList of interactive UIElements UIButton: Supports touch events like tapping and can be connected to IBAction for touchUpInside, touchDown, etc. UISwitch: Can trigger an IBAction when its on/off state changes. UISlider: Sends an action when its value changes due to user interaction. UISegmentedControl: Triggers an action when the selected segment changes. UIDatePicker: Triggers an action when the selected date changes. UITextField: Supports actions like editingDidEnd or editingChanged. UIPageControl: Triggers an action when the current page index changes. UIRefreshControl: Triggers an action when the user initiates a refresh in a scroll view. UINavigationItem and UIBarButtonItem: Both can have actions assigned via IBAction to handle navigation or toolbar events. Non interactive element To access non-interactive elements, we need to use their IBOutlet reference inside the view controller.\nTo add actions for these elements, we can use a UITapGestureRecognizer.\nIn this example, the view controller has a reference to a label in the storyboard via IBOutlet, and we can add the following code to enable interactivity.\nclass ExampleViewController: UIViewController { @IBOutlet weak var colorLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() addTapActionToLabel() // Add this function call } // More code // Add this function private func addTapActionToLabel() { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(labelTapped)) colorLabel.addGestureRecognizer(tapGesture) colorLabel.isUserInteractionEnabled = true } // Add this function @objc func labelTapped() { print(\u0026#34;Label tapped!\u0026#34;) } // More code } The IBOutlet mechanism is designed specifically to link UI elements, such as labels, buttons, and image views, to properties in your code, enabling you to access and modify these components programmatically after connecting them via a storyboard or Xib file.\nList of non interactive UI elements UILabel: Displays static or dynamic text but doesn\u0026rsquo;t respond to user interactions on its own. UIImageView: Shows images but isn\u0026rsquo;t designed to respond to taps or other gestures directly. UIView: A base view class that serves as a container for other elements and doesn\u0026rsquo;t have direct user interaction methods. UIStackView: Arranges child views in a horizontal or vertical stack but doesn\u0026rsquo;t have built-in user-triggered events. UITableViewCell: The individual cells in a table view that respond to user interaction only through their parent table view. UICollectionViewCell: Similar to table view cells, these collection view cells interact through the parent collection view. Instantiating Storyboards from another View Controller To instantiate programmatically a storyboard from another class we can follow the next approach.\nTo achieve this we need two pieces of information, the name of the storyboard file and the Storyboard ID. We use the first element in UIStoryboard and the second element in storyboard.instantiateViewController.\nWe can present or push the final object, in this example we are pushing the storyboard as a modal.\n// A different view controller that will instantiate the storyboard class ViewController: UIViewController { // More code override func viewDidAppear(_ animated: Bool) { presentStoryboard() } func presentStoryboard() { let storyboard = UIStoryboard(name: \u0026#34;ExampleStoryboard\u0026#34;, bundle: nil) if let exampleViewController = storyboard.instantiateViewController(withIdentifier: \u0026#34;ExampleSB\u0026#34;) as? ExampleViewController { present(exampleViewController, animated: true, completion: nil) } } // More code } Here is a small example of the resulting presentation.\nWorking with Xibs Working with Xibs is similar to working with Storyboards, so this section will feel familiar to the Storyboard section.\nCreating Xibs With an associated View Controller One simple way to create a Xib is during the creation of a View Controller.\nInside an open Xcode project press CMD + N to open the template window.\nThen give it a name and check the box Also create XIB file.\nAfter pressing Next, select a directory to create the files and check the target and group are correct.\nNow two files will be created, the Xib and the View Controller class.\nThese two files are now linked by default, so you can add IBActions and IBOutlets in your custom controller class to add some logic to the Xib.\nSimple example to control the Xib objects from code Let\u0026rsquo;s add and connect the following elements to the Xib. You can add the constrains as you like.\nThen let\u0026rsquo;s add some IBOutlet to the preconfigured UIViewController subclass (this process is the same as with the Storyboard, you can refer it here).\nclass ExampleXibViewController: UIViewController { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var subheadingLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() titleLabel.text = \u0026#34;Awesome Xib Example\u0026#34; subheadingLabel.numberOfLines = 0 subheadingLabel.text = \u0026#34;Learning how to integrate Xibs on your programmatic projects.\u0026#34; } } #Preview(traits: .sizeThatFitsLayout) { ExampleXibViewController() } Now you can see how the Xib is loaded automagically (this works because both files have the same name and the Xib was autoconfigured to have this class as its file owner) and thanks to the IBOutlet you can manage their state and actions using the references in the code.\nIndividually To create an individual Xib file, press CMD + N and select either View or Empty.\nFinally, we give a name, check the correct group and target, and press Create.\nChange Xib visual interface to freeform By default Xib interface resembles an iPhone screen, sometimes we are creating a View that will act as a component, in these cases having the iPhone layout is not the best idea.\nTo change to a freeform layout we can do the following\nOpen the right bar CMD + OPC + 0. In the right bar, select Show the attribute inspector. Under Simulated metrics select size. In the size menu select Freeform. Optional, change the dimensions of your Xib to your liking. Disable Safe Area for Freeform Xibs If we are using our Xibs as a component to the final screen, we may not want to have Safe area on by default, to turn it off we can do the following:\nOpen the right bar CMD + OPC + 0. In the right bar, select Show the size inspector. Under Layout Guides untick Safe Area. Adding constraints to Xibs This works the same as adding constraints to Storyboards, you can refer to that section here.\nConnecting Xibs to Classes Individual Xibs can be connected to Classes to add some logic to them.\nWe are covering these cases:\nConnecting to UIView subclass. Connecting to UIViewController subclass. Connecting to UITableViewCell or UICollectionViewCell subclasses. Connecting to UIView subclass We can bind a Xib with a UIView subclass, this process is a tad complicated than binding with UIViewControllers.\nFirst, let\u0026rsquo;s create the UIView subclass we are going to bind to the Xib.\nPress CMD + N and select Swift File. Give it a name and be sure to add it to the right group and target, press create. Create the class by adding the following code to the new created file. import UIKit class SimpleXibView: UIView { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } } #Preview(traits: .sizeThatFitsLayout) { SimpleXibView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) } Assign this class as the File's Owner Add the nib load method to the UIView custom class. We use this method to load the Xib with the same name as our UIView class, if your Xib has a different name update the method accordingly.\nprivate func loadFromNib() { guard let view = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? UIView else { return } view.frame = self.bounds view.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.addSubview(view) } Call the method in the init to load the Nib, your class should look like this. import UIKit class SimpleXibView: UIView { override init(frame: CGRect) { super.init(frame: frame) loadFromNib() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } private func loadFromNib() { guard let view = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? UIView else { return } view.frame = self.bounds view.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.addSubview(view) } } #Preview(traits: .sizeThatFitsLayout) { SimpleXibView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) } Preview the Nib on the Canvas! Now you have linked your Xib with a custom UIView subclass.\nYou can add IBOutlet or IBAction to manage the logic of your View. This mechanism doesn\u0026rsquo;t change from how we did it with Storyboards, here you can find out more.\nConnecting to UIViewController subclass Xibs are easy to connect to UIViewController.\nAssign the File's Owner to the desire custom class. CONTROL + CLICK on File's Owner and drag view to the view object on screen. The view property of the File's Owner only appear if the assigned class is a subclass of UIViewController.\nHere is a video showing the process.\nConnecting to UITableViewCell or UICollectionViewCell subclasses Working with tables deserves a separate entry, but to finish Xibs let\u0026rsquo;s give the steps needed to create and link cells.\nCreate the Xib File: In Xcode, right-click on the project navigator, select \u0026ldquo;New File\u0026hellip;\u0026rdquo;, choose \u0026ldquo;View\u0026rdquo; under the User Interface section, and name the Xib after the cell it represents, like CustomTableViewCell.xib.\nDesign the Interface: Open the Xib file, drag a UITableViewCell or UICollectionViewCell from the object library onto the canvas, and add UI elements like labels, images, buttons, etc.\nCreate the Cell Subclass: Create a new Cocoa Touch Class, subclass UITableViewCell or UICollectionViewCell as CustomTableViewCell, then go back to your Xib file, select the cell, and set the class to CustomTableViewCell in the Identity Inspector.\nConnect Outlets: With the Xib file and the Assistant Editor open (showing CustomTableViewCell.swift), Ctrl-drag from the UI elements in the Xib to the class file to create outlets and actions.\nRegister the Xib with the Table or Collection View: In your view controller’s viewDidLoad() method, register the Xib with let nib = UINib(nibName: \u0026quot;CustomTableViewCell\u0026quot;, bundle: nil) and tableView.register(nib, forCellReuseIdentifier: \u0026quot;CustomCellIdentifier\u0026quot;) for UITableView, or similar steps for UICollectionView.\nConclusion In this blog entry, we\u0026rsquo;ve embarked on a comprehensive exploration of Xibs and Storyboards within Xcode, unraveling their functionalities and best practices to enhance our iOS development skills. By dissecting their definitions and diving into practical implementation scenarios, we\u0026rsquo;ve equipped ourselves with the knowledge to effectively utilize these tools in creating both complex and component-specific user interfaces.\nThe step-by-step guides provided will serve as a valuable reference for integrating Xibs and Storyboards into your projects, ensuring you can confidently handle layout constraints, link UI elements to code, and streamline your development process. Whether you\u0026rsquo;re crafting a single view or orchestrating an entire app\u0026rsquo;s flow, the insights shared here aim to bolster your proficiency and creativity in using these powerful features of Xcode.\nAs we continue to evolve our skills in the iOS development landscape, let\u0026rsquo;s leverage the flexibility and functionality of Xibs and Storyboards to create more dynamic, efficient, and visually compelling applications when working with UIKit.\n","date":"2024-05-05T05:40:28Z","permalink":"/p/working-with-xibs-and-storyboards/","title":"Working With Xibs And Storyboards"},{"content":"Hello world, I\u0026rsquo;m back with a new entry on UIKit. In iOS 17 Apple added the ability to preview our views and ViewControllers using the SwiftUI canvas. This brings the possibility of faster programmatic UIKir programming. Let\u0026rsquo;s make a small project that illustrates how to leverage this new functionality and revising how we can create an app in UIKit 100% programmatically. 🚀\nThe app We will be coding an app that will ask for user information and then send it back to the main ViewController, this will showing us a simple way to pass information between views, a small introduction intro programmatic navigation and view reusability.\nConfiguring the project to work without Storyboard Deleting storyboard files and references UIKit only works on iOS and iPadOS, so you\u0026rsquo;ll need to create an iOS app in Xcode and select Storyboard, but don\u0026rsquo;t worry we\u0026rsquo;re going to delete all the stuff related to it. We\u0026rsquo;re going full code this time.\nSelect iOS App and Storyboard for the interface, remember Storyboard means UIKit under the hood.\nOnce Xcode is open with our project we need to delete all the stuff related to storyboard configuration.\nFirst, let\u0026rsquo;s delete the Storyboard by moving it to the trash (you can leave the LaunchScreen).\nNow let\u0026rsquo;s delete the references for the deleted Storyboard, follow the images and delete the selected sections.\nCongratulations, you have deleted storyboards from your UIKit project. Now run the app and see a cool black screen.\nInstantiate our navigation and set the root view controller Now it\u0026rsquo;s time to instantiate and show our first screen!\nOpen SceneDelegate.swift and locate the function func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions), next replace the code inside the function with this.\nguard let windowScene = (scene as? UIWindowScene) else { return } let viewController = ViewController() let navigationController = UINavigationController(rootViewController: viewController) window = UIWindow(windowScene: windowScene) window?.rootViewController = navigationController window?.makeKeyAndVisible() Here is an explanation of the code above:\nScene Detection and Guard Clause: guard let windowScene = (scene as? UIWindowScene) else { return }\nThis line ensures the scene parameter is of type UIWindowScene. If not, it exits the block of code to prevent errors. This is essential for apps supporting multiple scenes in iOS 13 and later.\nView Controller Initialization: let viewController = ViewController()\nCreates a new instance of ViewController, which should be a subclass of UIViewController. This controller will manage the app\u0026rsquo;s initial view.\nNavigation Controller Setup: let navigationController = UINavigationController(rootViewController: viewController)\nInitializes a UINavigationController with the viewController as its root. This setup provides a navigation bar and a stack-based navigation mechanism.\nWindow Initialization: window = UIWindow(windowScene: windowScene)\nCreates a new UIWindow using the windowScene from the first line, setting up the environment where the app’s content will be displayed.\nSetting the Root View Controller: window?.rootViewController = navigationController\nAssigns the navigationController as the window\u0026rsquo;s root view controller, making viewController the first visible view.\nMaking the Window Key and Visible: window?.makeKeyAndVisible()\nThis method sets the window as the key window and ensures it is visible, making it the main focus for user interactions.\nEach ViewController has an associated optional NavigationController, this object is used to manage the push and pop of different ViewControllers, some architectures like to manage this navigation using a coordinator.\nCongratulations on having the basic components to navigate your app and show your first screen.\nCreating the Model The app is quite simple, the model just consist of two Strings that will be change according to what the user inputs in the form.\nTo create a Model we just need to create a struct with the fields we need. Create a new file called TrainerInfo.swift and add the following code.\nimport Foundation struct TrainerInfo: Identifiable { let id: UUID var name: String var favoritePokemon: String } extension TrainerInfo: CustomStringConvertible { var description: String { \u0026#34;Trainer \\(name) likes \\(favoritePokemon)\u0026#34; } } This model will store the trainer\u0026rsquo;s name and their favorite Pokémon. The extension is a good way to get a string representation of the object, we will use this string later in our views.\nCreating reusable components Our app is quite simple, yet we are using several repeated components in our Views, to avoid repeating code and ensure the UI elements behave the same way through the app we are going to create simple reusable components. To do this we are going to extend the class element of the component we need and add a function that will create the component configured for our use.\nLet\u0026rsquo;s start by creating a new Group called Extensions by pressing CMD + OPC + A, inside this new group let\u0026rsquo;s create a new file called UIExtensions.swift and add the following code:\nimport UIKit extension UIButton { static func makeSimpleButton(title: String, action: UIAction) -\u0026gt; UIButton { let button = UIButton() button.setTitle(title, for: .normal) button.addAction(action, for: .touchUpInside) button.configuration = UIButton.Configuration.filled() button.translatesAutoresizingMaskIntoConstraints = false return button } } extension UILabel { static func makeSimpleLabel( text: String = \u0026#34;\u0026#34;, textAlignment: NSTextAlignment = .center, font: UIFont = .preferredFont(forTextStyle: .body) ) -\u0026gt; UILabel { let label = UILabel() label.textAlignment = textAlignment label.text = text label.font = font label.translatesAutoresizingMaskIntoConstraints = false return label } } extension UIStackView { static func makeSimpleStack(axis: NSLayoutConstraint.Axis ) -\u0026gt; UIStackView { let stack = UIStackView() stack.axis = axis stack.translatesAutoresizingMaskIntoConstraints = false return stack } } extension UITextField { static func makeSimpleTextField(delegate: (any UITextFieldDelegate)?) -\u0026gt; UITextField { let textField = UITextField() textField.borderStyle = .roundedRect // Don\u0026#39;t forget to add delegate to call the function that sends data back textField.delegate = delegate textField.translatesAutoresizingMaskIntoConstraints = false return textField } } Done, now we can use these methods to create preconfigured UI elements, we can configure further to tailor our needs, if the systems grows quite complex we could even implement Factories. This is a great way to avoid repeating code and ensuring UI uniformity for our app.\nCreating our first screen Now that we have all the needed code in place, it\u0026rsquo;s time to see some changes in our phone\u0026rsquo;s screen. Let\u0026rsquo;s open ViewController.swift and let\u0026rsquo;s start adding our UI elements!\nFor this screen we are going to use a UILabel and a UIButton, it\u0026rsquo;s time to use our handy make methods we defined in our previous step.\nIn the body class, let\u0026rsquo;s add the following properties that will contain the references to the UI elements.\nprivate lazy var button: UIButton = .makeSimpleButton(title: \u0026#34;Add Trainer Data\u0026#34;, action: buttonAction) private lazy var label: UILabel = .makeSimpleLabel() Now you will get an error because buttonAction is not defined, let\u0026rsquo;s fix that now!\nCreate a private extension of your ViewController class and add the missing method there.\nprivate extension ViewController { var buttonAction: UIAction { UIAction { [weak self] action in print(\u0026#34;Button pressed\u0026#34;) } } } This should fix the error. Later on, we will add the logic for the pressed button.\nPreviewing your ViewController iOS 17 allow us to see in the Canvas the current state of our ViewController. This addition makes creating UIKit code easier and faster since we can preview in realtime our changes.\nTo use this new feature we just need to add the following at the bottom of our ViewController.swift file. This uses the new macro features to creates a preview canvas that instantiate a copy of our view controller\n#Preview { ViewController() } Now you will have this white iPhone on your right.\nBut wait, we just added two UI components to our ViewController, why aren\u0026rsquo;t they visible? This is because we need to tell the view controller where we want to place them in the View. to achieve this we need to use a nifty tool called Auto Layout.\nAdding constraints to our UI elements We have to elements loaded on memory now it\u0026rsquo;s time to define where in the view they will be and add them to the current View.\nTo make our process of adding these elements to the current view lets create a simple extension, place it in the UIExtensions file.\nextension UIStackView { func addArrangedSubviews(_ views: [UIView]) { views.forEach { self.addArrangedSubview($0) } } } To make Auto Layout to work on our UI elements we need to remember to set translatesAutoresizingMaskIntoConstraints to false.\nNow it\u0026rsquo;s time to add and configure how the UI will be displayed on the View.\nTo make this process easy, let\u0026rsquo;s make a method that will do this for us. Inside the ViewController class we need to add the following functions.\nfunc configureView() { view.backgroundColor = .systemBackground view.addSubviews([button, label]) configureConstraints() } func configureConstraints() { let buttonConstraints = [ button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.centerYAnchor.constraint(equalTo: view.centerYAnchor), label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 64), label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16) ] NSLayoutConstraint.activate(buttonConstraints) } Understanding Auto Layout When creating user interfaces in iOS under UIKit, Auto Layout is used to calculate the size and position of views based on the constraints we specify.\nThe code above establishes rules for placing a button and a label within their container view, centering the button and positioning the label near the top and stretching it from side to side. These constraints ensure that the UI elements are correctly laid out across different devices and orientations.\nSetting Up the Scene Here is a small analogy that I hope can help you to understand better the Auto Layout code:\nImagine you\u0026rsquo;re arranging furniture in a room (the view), with a chair (button) and a painting (label). You want these items to be placed according to specific rules:\nCentering the Chair:\nbutton.centerXAnchor.constraint(equalTo: view.centerXAnchor): Align the chair\u0026rsquo;s center with the room\u0026rsquo;s horizontal center. button.centerYAnchor.constraint(equalTo: view.centerYAnchor): Align the chair\u0026rsquo;s center with the room\u0026rsquo;s vertical center. Positioning the Painting:\nlabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 64): Place the top of the painting slightly below the top of the safe area (like giving it some space from the ceiling). label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16): Set the painting\u0026rsquo;s left edge a bit away from the room\u0026rsquo;s left wall. label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16): Set the painting\u0026rsquo;s right edge a bit away from the room\u0026rsquo;s right wall. Applying the Rules NSLayoutConstraint.activate(buttonConstraints): This activates the constraints, like finalizing the arrangement and instructing the system to place the furniture accordingly. Remeber to call configureView() inside viewDidLoad or this code will never execute!\nNow you can get your UI in the Canvas, congratulations!\nIf run the app and try to press the button nothing interesting will happen, let\u0026rsquo;s fix that next!.\nCreating the Second Screen The second screen will contain a Form that will ask for certain date to the user, this form is a great way to introduce reusable UIView\nCreating the Reusable Field View To make our reusable view we need to create a UIView.\nCreate a new Swift file and call it FormSectionView.swift, inside that file let\u0026rsquo;s create our class that inherit from UIView (all views in UIKit inherit from this class).\nfinal class FormSectionView: UIView { // Our code will be here } Our View needs a way to communicate internal information outward and to receive information from the outside. To solve this problem we have several common methods in UIKit:\nDelegation Closures Notification Center In this example let\u0026rsquo;s explore using closures.\nCreating the communication path To receive information we need a closure that returns the information from the outside when this changes.\nTo send information we need to send the information as the closure parameter so the outside world can get and manipulate this information.\nFor our app we are receiving and sending text from this View.\nLet\u0026rsquo;s define the communication path, inside FormSectionView class define the following variables.\nvar textDidChange: ((String) -\u0026gt; Void)? var sectionTitle: (() -\u0026gt; String)? { didSet { sectionLabel.text = sectionTitle?() } } Here is what the code is doing:\nUnderstanding the textDidChange Closure Purpose: This property is a closure that takes a string as input and performs an action with no return value (Void). It\u0026rsquo;s designed to execute when text data changes, allowing for dynamic responses to user input or programmatic changes.\nUsage: You use textDidChange to update other components of your application when the user modifies the text in a UITextField. This closure provides a way to react immediately to changes, facilitating actions like validation, interface updates, or data transformations without needing additional delegation or observation mechanisms.\nDynamic Title Setting with sectionTitle Purpose: sectionTitle is a closure that, when called, returns a String representing the title of a section. It\u0026rsquo;s used to dynamically provide text for UI labels.\nBehavior on Change (didSet): The didSet observer attached to sectionTitle automatically updates the sectionLabel.text each time the closure\u0026rsquo;s value is set or updated. The safe call sectionTitle?() ensures that the closure is only called if it\u0026rsquo;s not nil, preventing runtime errors.\nPractical Use: This property can be particularly useful in adaptive UIs where the section title might change based on user interactions or other application logic. By tying the label text directly to a closure, you maintain flexibility and reduce the need for explicit refresh or update calls throughout your codebase.\nCreating the UI elements You may have noticed that the code inside the didSet failed because we didn\u0026rsquo;t add any UI to our UIView. Let\u0026rsquo;s fix that by adding the UI elements from this View.\nAs you remember, we extended some UI elements to create some preconnfigured objects, this will come handy in this class too.\nInside FormSectionView class add the following elements.\nprivate lazy var sectionLabel: UILabel = .makeSimpleLabel( text: \u0026#34;No Text Received, sectionTitle not set.\u0026#34;, textAlignment: .natural, font: .systemFont(ofSize: 24, weight: .semibold) ) private lazy var sectionTextField: UITextField = .makeSimpleTextField(delegate: self) These are the two elements we need to make our view, as before we also need to use Auto Layout to position them on the screen and add the elements so the View knows about them, to do that add this code at the bottom of the file.\nprivate extension FormSectionView { func configureView() { addSubviews([sectionLabel, sectionTextField]) configureConstraints() } func configureConstraints() { let sectionLabelConstraints = [ sectionLabel.topAnchor.constraint(equalTo: topAnchor), sectionLabel.leadingAnchor.constraint(equalTo: leadingAnchor), sectionLabel.trailingAnchor.constraint(equalTo: trailingAnchor) ] let sectionTextFieldConstraints = [ sectionTextField.topAnchor.constraint(equalTo: sectionLabel.bottomAnchor), sectionTextField.leadingAnchor.constraint(equalTo: leadingAnchor), sectionTextField.trailingAnchor.constraint(equalTo: trailingAnchor), sectionTextField.bottomAnchor.constraint(equalTo: bottomAnchor) ] let constraints = sectionLabelConstraints + sectionTextFieldConstraints NSLayoutConstraint.activate(constraints) } } In ViewControllers we usually place the configuration in viewDidLoad but since this is a View, we don\u0026rsquo;t have that life cycle so we need to use a normal init. Add the following code to the class.\noverride init(frame: CGRect) { super.init(frame: frame) configureView() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } In UIKit, the required init?(coder: NSCoder) initializer is a crucial component of the view and view controller initialization process when they are being instantiated from storyboards or XIB files.\nTo finalize our view we need a way to know when the text in the UITextField change, to achieve this we can use a delegate of this class. This is way we set this View as the delegate of the custom UITextField.\nAdd the following extension at the end of the file.\nextension FormSectionView: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -\u0026gt; Bool { guard let currentText = textField.text, let textRange = Range(range, in: currentText) else { return false } let updatedText = currentText.replacingCharacters(in: textRange, with: string) textDidChange?(updatedText) return true } } Function textField(_:shouldChangeCharactersIn:replacementString:): This delegate method is called whenever the user types a character, deletes a character, or pastes content into the text field. It asks the delegate (in this case, FormSectionView) if the specified text should be changed. Inside the Function Parameters: textField: The text field containing the text. range: The range of characters to be replaced. string: The replacement string that is typed or pasted into the text field. Guard Statement: Checks if it can obtain the current text of the text field and convert the NSRange to a Range\u0026lt;String.Index\u0026gt; suitable for Swift string operations. If it can\u0026rsquo;t, it returns false, preventing the change to the text field\u0026rsquo;s content. Updating Text: updatedText: A new string is created by replacing the characters within the specified range with the new string. This is done using replacingCharacters(in:with:), which handles the text replacement safely, considering character encoding and composition. Closure Call: textDidChange?(updatedText): If the textDidChange closure is set, it\u0026rsquo;s called with the updatedText. This allows the class to react to text changes, potentially updating models, views, or triggering other logic. Return Value: Returns true to allow the text change to proceed. If you return false, the text field would not update its display to reflect the new text. Previewing the View As before, we can preview our View thanks to the canvas.\nAdd the following code at the end of the file to activate the Preview.\n#Preview(traits: .sizeThatFitsLayout) { FormSectionView() } Here is our view!\nCreating the Form View using the Reusable View component It\u0026rsquo;s time to create the Form View that will be present in the view controller, thanks to the reusable view we can simplify the creation of this view.\nCreate a new Swift file called TrainerFormView.swift and add the following code.\nimport UIKit final class TrainerFormView: UIView { } Next, let\u0026rsquo;s add the UI elements we need for this.\nAdd the following inside the class.\nprivate lazy var headerTitle: UILabel = .makeSimpleLabel( text: \u0026#34;Trainer Form\u0026#34;, font: .preferredFont(forTextStyle: .extraLargeTitle) ) private lazy var formStack: UIStackView = .makeSimpleStack(axis: .vertical) private lazy var saveButton: UIButton = .makeSimpleButton( title: \u0026#34;Save Trainer Data\u0026#34;, action: saveAction ) The button\u0026rsquo;s action saveAction will mark an error, we need to define this action. As before let\u0026rsquo;s create a private extension and add the action.\nprivate extension TrainerFormView { var saveAction: UIAction { UIAction { [weak self] action in guard let self else { return } self.saveButtonAction?(self.trainerData) } } And add the following property inside the class, we are using this tuple as a way to store the current state of the entered data, this represents an instance of our model.\nprivate var trainerData: TrainerInfo = .init(id: UUID(), name: \u0026#34;Name\u0026#34;, favoritePokemon: \u0026#34;Pokemon\u0026#34;) Adding the custom fields to the stack Stacks are a great way to add elements in a ordered way. In our case we are using them to store the custom text fields.\nLet\u0026rsquo;s configure the stack with the UI we need.\nInside private extension TrainerFormView add the following function.\nfunc configureStack() { let nameSection = FormSectionView() nameSection.sectionTitle = { \u0026#34;Trainer Name\u0026#34; } nameSection.textDidChange = { [weak self] text in self?.trainerData.name = text } let pokemonSection = FormSectionView() pokemonSection.sectionTitle = { \u0026#34;Favorite Pokémon\u0026#34;} pokemonSection.textDidChange = { [weak self] text in self?.trainerData.favoritePokemon = text } formStack.addArrangedSubviews([nameSection, pokemonSection]) formStack.spacing = 16 } Here we are setting the closure for communication, passing the text name and receiving the input data to then store it the tuple we have in this View.\nAdding constraints So far our elements cannot be rendered in the View because we still need to add them. As usual we need to set the Auto Layout for these elements, add the following function inside private extension TrainerFormView.\nfunc configureConstraints() { let headerTitleConstraints = [ headerTitle.topAnchor.constraint(equalTo: topAnchor), headerTitle.leadingAnchor.constraint(equalTo: leadingAnchor), headerTitle.trailingAnchor.constraint(equalTo: trailingAnchor) ] let formStackConstraints = [ formStack.topAnchor.constraint(equalTo: headerTitle.bottomAnchor, constant: 32), formStack.leadingAnchor.constraint(equalTo: leadingAnchor), formStack.trailingAnchor.constraint(equalTo: trailingAnchor) ] let saveButtonConstraints = [ saveButton.topAnchor.constraint(equalTo: formStack.bottomAnchor, constant: 32), saveButton.heightAnchor.constraint(equalToConstant: 48), saveButton.leadingAnchor.constraint(equalTo: leadingAnchor), saveButton.trailingAnchor.constraint(equalTo: trailingAnchor) ] let viewConstraints = headerTitleConstraints + formStackConstraints + saveButtonConstraints NSLayoutConstraint.activate(viewConstraints) } Adding the init To make all this work we need to add the init and call the configuration functions, like in the previous UIView we need to add the following to the class.\noverride init(frame: CGRect) { super.init(frame: frame) addSubviews([headerTitle, formStack, saveButton]) configureConstraints() configureStack() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } If you don\u0026rsquo;t add the subviews before the constraints the simulator will crash.\nPreviewing the result As normal, we can preview the view by using the canvas. Add the following code at the end of the file.\n#Preview(traits: .sizeThatFitsLayout) { TrainerFormView() } Here is the final View!\nCreating the FormViewController The View Form is read, now we need a Controller that manages it!, it\u0026rsquo;s time to create our FormViewController.\nAdd a new file called FormViewController.swift and add the following code.\nimport UIKit class FormViewController: UIViewController { } Adding the View We could create a factory that help us to create the Views but this time let\u0026rsquo;s use a simple closure that returns the View.\ninside the class add the following UI element (the view we just created).\nlazy var trainerForm: TrainerFormView = { let form = TrainerFormView() return form }() We also need a way to communicate the result of our View Form to any parent view controller, to achieve this we are using again a closure with the data received from the view.\nAdd this to the class.\nvar saveAction: ((TrainerInfo) -\u0026gt; Void)? As always we need to add the view (we could override the default view also) and set the constraints, here we are also setting the closure that will receive the data from the View.\nThis closure will call the closure of our ViewController and send the information to the next object that needs this information.\nThe dismiss is used to pop this view from the screen.\nUsually we have two way of presenting screens in iOS:\nWith a Push With a Modal To do that add this function to the class.\nprivate func configureView() { trainerForm.translatesAutoresizingMaskIntoConstraints = false trainerForm.saveButtonAction = { [weak self] trainerData in self?.saveAction?(trainerData) self?.dismiss(animated: true) } view.addSubview(trainerForm) NSLayoutConstraint.activate([ trainerForm.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), trainerForm.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), trainerForm.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), trainerForm.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16) ]) } Wiring up the ViewController To finish this ViewController just call the functions inside the viewDidLoad.\noverride func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground configureView() } As always you can also add a Preview, add at the end of the file.\n#Preview { FormViewController() } Congratulations, you have the second view controller ready to use!\nNext step is to present it from the first view controller and setting up the communication path.\nPresenting the form Open ViewController.swift and let\u0026rsquo;s modify the button action. Locate the var buttonAction: UIAction computed proerty and let\u0026rsquo;s modify it to have this code instead.\nvar buttonAction: UIAction { UIAction { [weak self] action in let formViewController = FormViewController() formViewController.saveAction = { [weak self] trainerData in self?.label.text = trainerData.description } self?.present(formViewController, animated: true) } } Here, we are updating the UIAction, we are creating the controller we want to present and configure its closure. then we use the present() method to present modally the view controller.\nIn the closure we are receiving the data every time we press the button Save Trainer Data in the FormViewController. We are using this data to update the label in this ViewController.\nBy doing this change the app is ready.\nPress run and let\u0026rsquo;s test it!\nTesting the final app Here is a video of the resulting app.\nCongratulations, you made it to the end!\nConclusion In this blog post, we\u0026rsquo;ve explored the benefits of programmatic UIKit development in iOS 17, showcasing the power and flexibility of bypassing storyboards to create dynamic user interfaces directly in code. By delving into practical examples—from configuring the project environment to creating reusable components and leveraging the SwiftUI canvas for real-time previews—we have demonstrated how a UIKit app can be constructed from the ground up with clarity and precision.\nThe ability to preview UIViews and ViewControllers within Xcode enhances development by providing immediate visual feedback, a feature that significantly streamlines the process of building and refining user interfaces. Moreover, our dive into the creation of reusable components not only promotes cleaner, more maintainable code but also fosters a modular approach to UI development that can be easily adapted or expanded upon.\nBy the end of our project, we effectively utilized closures and extensions to keep our codebase efficient and readable, and demonstrated the seamless flow of data between views, emphasizing the robustness of UIKit in handling complex user interactions.\nAs iOS continues to evolve, embracing these programmatic techniques provides a solid foundation for both new and experienced developers to build sophisticated and responsive apps. Whether you are transitioning from storyboards or enhancing your existing skills, the insights from this tutorial will undoubtedly aid in your journey towards mastering programmatic UIKit development. Happy coding! 🚀\nAppendix Here are the full code separated by files:\nControllers ViewController final class ViewController: UIViewController { // MARK: - UI Elements private lazy var button: UIButton = .makeSimpleButton(title: \u0026#34;Add Trainer Data\u0026#34;, action: buttonAction) private lazy var label: UILabel = .makeSimpleLabel() // MARK: - ViewController Life Cycle override func viewDidLoad() { super.viewDidLoad() // To show the title I need a navigation controller initialized title = \u0026#34;Trainer Log\u0026#34; configureView() } } // MARK: - Private Methods private extension ViewController { var buttonAction: UIAction { UIAction { [weak self] action in let formViewController = FormViewController() formViewController.saveAction = { [weak self] trainerData in self?.label.text = trainerData.description } self?.present(formViewController, animated: true) } } func configureView() { view.backgroundColor = .systemBackground view.addSubviews([button, label]) configureConstraints() } func configureConstraints() { let buttonConstraints = [ button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.centerYAnchor.constraint(equalTo: view.centerYAnchor), label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 64), label.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), label.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16) ] NSLayoutConstraint.activate(buttonConstraints) } } FormViewController class FormViewController: UIViewController { var saveAction: ((TrainerInfo) -\u0026gt; Void)? private lazy var trainerForm: TrainerFormView = { let form = TrainerFormView() return form }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground configureView() } private func configureView() { trainerForm.translatesAutoresizingMaskIntoConstraints = false trainerForm.saveButtonAction = { [weak self] trainerData in self?.saveAction?(trainerData) self?.dismiss(animated: true) } view.addSubview(trainerForm) NSLayoutConstraint.activate([ trainerForm.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16), trainerForm.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16), trainerForm.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16), trainerForm.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16) ]) } } Extensions import UIKit extension UIView { /// Add the array of subviews to the current View /// - Parameter views: The views to add func addSubviews(_ views: [UIView]) { views.forEach { self.addSubview($0) } } } extension UIStackView { func addArrangedSubviews(_ views: [UIView]) { views.forEach { self.addArrangedSubview($0) } } } extension UIButton { static func makeSimpleButton(title: String, action: UIAction) -\u0026gt; UIButton { let button = UIButton() button.setTitle(title, for: .normal) button.addAction(action, for: .touchUpInside) button.configuration = UIButton.Configuration.filled() button.translatesAutoresizingMaskIntoConstraints = false return button } } extension UILabel { static func makeSimpleLabel( text: String = \u0026#34;\u0026#34;, textAlignment: NSTextAlignment = .center, font: UIFont = .preferredFont(forTextStyle: .body) ) -\u0026gt; UILabel { let label = UILabel() label.textAlignment = textAlignment label.text = text label.font = font label.translatesAutoresizingMaskIntoConstraints = false return label } } extension UIStackView { static func makeSimpleStack(axis: NSLayoutConstraint.Axis ) -\u0026gt; UIStackView { let stack = UIStackView() stack.axis = axis stack.translatesAutoresizingMaskIntoConstraints = false return stack } } extension UITextField { static func makeSimpleTextField(delegate: (any UITextFieldDelegate)?) -\u0026gt; UITextField { let textField = UITextField() textField.borderStyle = .roundedRect // Don\u0026#39;t forget to add delegate to call the function that sends data back textField.delegate = delegate textField.translatesAutoresizingMaskIntoConstraints = false return textField } } Views TrainerFormView import UIKit final class TrainerFormView: UIView { private var trainerData: TrainerInfo = .init(id: UUID(), name: \u0026#34;Name\u0026#34;, favoritePokemon: \u0026#34;Pokemon\u0026#34;) var saveButtonAction: ((TrainerInfo) -\u0026gt; Void)? private lazy var headerTitle: UILabel = .makeSimpleLabel( text: \u0026#34;Trainer Form\u0026#34;, font: .preferredFont(forTextStyle: .extraLargeTitle) ) private lazy var formStack: UIStackView = .makeSimpleStack(axis: .vertical) private lazy var saveButton: UIButton = .makeSimpleButton( title: \u0026#34;Save Trainer Data\u0026#34;, action: saveAction ) override init(frame: CGRect) { super.init(frame: frame) // If you don\u0026#39;t add the subviews before the constraints the simulator will crash addSubviews([headerTitle, formStack, saveButton]) configureConstraints() configureStack() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } } private extension TrainerFormView { func configureConstraints() { let headerTitleConstraints = [ headerTitle.topAnchor.constraint(equalTo: topAnchor), headerTitle.leadingAnchor.constraint(equalTo: leadingAnchor), headerTitle.trailingAnchor.constraint(equalTo: trailingAnchor) ] let formStackConstraints = [ formStack.topAnchor.constraint(equalTo: headerTitle.bottomAnchor, constant: 32), formStack.leadingAnchor.constraint(equalTo: leadingAnchor), formStack.trailingAnchor.constraint(equalTo: trailingAnchor) ] let saveButtonConstraints = [ saveButton.topAnchor.constraint(equalTo: formStack.bottomAnchor, constant: 32), saveButton.heightAnchor.constraint(equalToConstant: 48), saveButton.leadingAnchor.constraint(equalTo: leadingAnchor), saveButton.trailingAnchor.constraint(equalTo: trailingAnchor) ] let viewConstraints = headerTitleConstraints + formStackConstraints + saveButtonConstraints NSLayoutConstraint.activate(viewConstraints) } var saveAction: UIAction { UIAction { [weak self] action in // If I don\u0026#39;t safe unwrap self this wont work because trainerData despite not being optional // by the weak self it becomes optional when accessing via self?. . guard let self else { return } self.saveButtonAction?(self.trainerData) } } func configureStack() { let nameSection = FormSectionView() nameSection.sectionTitle = { \u0026#34;Trainer Name\u0026#34; } nameSection.textDidChange = { [weak self] text in self?.trainerData.name = text } let pokemonSection = FormSectionView() pokemonSection.sectionTitle = { \u0026#34;Favorite Pokémon\u0026#34;} pokemonSection.textDidChange = { [weak self] text in self?.trainerData.favoritePokemon = text } formStack.addArrangedSubviews([nameSection, pokemonSection]) formStack.spacing = 16 } } #Preview(traits: .sizeThatFitsLayout) { TrainerFormView() } FormSectionView import UIKit final class FormSectionView: UIView { var textDidChange: ((String) -\u0026gt; Void)? var sectionTitle: (() -\u0026gt; String)? { didSet { sectionLabel.text = sectionTitle?() } } private lazy var sectionLabel: UILabel = .makeSimpleLabel( text: \u0026#34;No Text Received, sectionTitle not set.\u0026#34;, textAlignment: .natural, font: .systemFont(ofSize: 24, weight: .semibold) ) private lazy var sectionTextField: UITextField = .makeSimpleTextField(delegate: self) override init(frame: CGRect) { super.init(frame: frame) configureView() } required init?(coder: NSCoder) { fatalError(\u0026#34;init(coder:) has not been implemented\u0026#34;) } } private extension FormSectionView { func configureView() { addSubviews([sectionLabel, sectionTextField]) configureConstraints() } func configureConstraints() { let sectionLabelConstraints = [ sectionLabel.topAnchor.constraint(equalTo: topAnchor), sectionLabel.leadingAnchor.constraint(equalTo: leadingAnchor), sectionLabel.trailingAnchor.constraint(equalTo: trailingAnchor) ] let sectionTextFieldConstraints = [ sectionTextField.topAnchor.constraint(equalTo: sectionLabel.bottomAnchor), sectionTextField.leadingAnchor.constraint(equalTo: leadingAnchor), sectionTextField.trailingAnchor.constraint(equalTo: trailingAnchor), sectionTextField.bottomAnchor.constraint(equalTo: bottomAnchor) ] let constraints = sectionLabelConstraints + sectionTextFieldConstraints NSLayoutConstraint.activate(constraints) } } extension FormSectionView: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -\u0026gt; Bool { guard let currentText = textField.text, let textRange = Range(range, in: currentText) else { return false } let updatedText = currentText.replacingCharacters(in: textRange, with: string) textDidChange?(updatedText) return true } } #Preview(traits: .sizeThatFitsLayout) { FormSectionView() } Model TrainerInfo import Foundation struct TrainerInfo: Identifiable { let id: UUID var name: String var favoritePokemon: String } extension TrainerInfo: CustomStringConvertible { var description: String { \u0026#34;Trainer \\(name) likes \\(favoritePokemon)\u0026#34; } } SceneDelegate modified function func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let viewController = ViewController() let navigationController = UINavigationController(rootViewController: viewController) window = UIWindow(windowScene: windowScene) window?.rootViewController = navigationController window?.makeKeyAndVisible() } ","date":"2024-04-16T01:27:55Z","permalink":"/p/introduction-to-programmatic-uikit/","title":"Introduction to Programmatic UIKit"},{"content":"Hello everyone. Lately, I\u0026rsquo;ve been experimenting with deep links and SwiftUI.\nIn the realm of iOS development, deep linking emerges as a powerful and indispensable tool, offering developers the means to seamlessly connect users to specific content or features within their applications.\nAt its core, deep linking allows for the precise navigation to a particular section of an app, rather than merely launching the app\u0026rsquo;s home screen.\nUnderstanding the fundamentals of iOS deep linking is pivotal for enhancing user experience, promoting user engagement, and, ultimately, unlocking the full potential of your mobile applications.\nIn today\u0026rsquo;s tutorial, we\u0026rsquo;re going to create a simple iOS app with several screens and configure the deeplinking mechanism to access and send data to those parts of our app.\nSo, grab your coding gear, fire up Xcode, and let\u0026rsquo;s create a new iOS project!\nDeeplinking App Example Configuring our project to handle deep links The first step is related to some configuration that will allow deep links to work in our app. To do this configuration, follow the steps below:\nOpen your Xcode project file. Select your target. Select the Info tab. Expand URL Types section. Press the + button to add a new URL Type Fill in the Identifier and Schema The Identifier is used to uniquely identify your app in the system, so it\u0026rsquo;s a good idea to use your reverse domain and the name of your app. The URL Schema is the url used to access your app from other parts of the system, normally we use it as yourschema://action?param1=val1\u0026amp;param2=val2.\nCreating the Data Model Let\u0026rsquo;s continue with the creation of an enum that will handle the shape of our data. In this app we have 3 Views that are coordinated with a TabView, so we could define 3 possible states for each one and use the power of enums to define the data state for each view.\nLet\u0026rsquo;s create a file called DeepLink.swift and inside add the following code:\nimport Foundation // define the deep link options and associated tag value for each view in the tab view enum DeepLink { case first case second case third var tag: Int { switch self { case .first: 1 case .second: 2 case .third: 3 } } var action: String { switch self { case .first: \u0026#34;first\u0026#34; case .second: \u0026#34;second\u0026#34; case .third: \u0026#34;third\u0026#34; } } var icon: String { switch self { case .first: \u0026#34;1.lane\u0026#34; case .second: \u0026#34;2.lane\u0026#34; case .third: \u0026#34;3.lane\u0026#34; } } var title: String { self.action.capitalized } } extension DeepLink { static let scheme = \u0026#34;deeplink\u0026#34; } The enum contains the three cases we discussed earlier and returns the specific data needed for each view in each computed property. Finally, we\u0026rsquo;re adding a static property to store the scheme we will be accepting in our app. By doing this we can avoid problems when typing the different strings, and it also serve us as a single source of truth for our data.\nCreating the Views Let\u0026rsquo;s continue working on the Views we are going to access via deep links. These are simple views that will display a text, the last one will accept data from outside.\nCreate the files FirstView.swift, SecondView.swift, ThirdView.swift, then add the following code to each file respectively:\n// Add this to FirstView.swift import SwiftUI struct FirstView: View { var body: some View { ZStack { Color.green .ignoresSafeArea() Text(\u0026#34;First\u0026#34;) .font(.largeTitle) .fontWeight(.bold) } } } #Preview { FirstView() } // Add this to SecondView.swift import SwiftUI struct SecondView: View { var body: some View { ZStack { Color.yellow .ignoresSafeArea() Text(\u0026#34;Second\u0026#34;) .font(.largeTitle) .fontWeight(.bold) } } } #Preview { SecondView() } // Add this to ThirdView.swift import SwiftUI struct ThirdView: View { let message: String? var body: some View { ZStack { Color.orange .ignoresSafeArea() VStack { Text(\u0026#34;Third\u0026#34;) .font(.largeTitle) .fontWeight(.bold) Text(message ?? \u0026#34;No message\u0026#34;) .font(.headline) } } } } #Preview { ThirdView(message: \u0026#34;Hello World\u0026#34;) } Now let\u0026rsquo;s add the TabView that will handle the navigation between the three views. We are going to reuse the ContentView.swift file to do this. Open it and replace the code inside with this:\n/* To test the deep links you can use Safari and enter the url with the schema and action defined here example: [Schema] [Action] [query variables] deeplink://first deeplink://second deeplink://third deeplink://third?msg=Hello%20from%20Safari */ import SwiftUI struct ContentView: View { // our deep link will change this variable @State private var selectedTab: Int = DeepLink.first.tag // Stores the message sent from the deep link @State private var message: String? = nil var body: some View { TabView(selection: $selectedTab) { FirstView() .tabItem { Label(DeepLink.first.title, systemImage: DeepLink.first.icon) } .tag(DeepLink.first.tag) // we use the tag to navigate in the TabView SecondView() .tabItem { Label(DeepLink.second.title, systemImage: DeepLink.second.icon) } .tag(DeepLink.second.tag) ThirdView(message: message) .tabItem { Label(DeepLink.third.title, systemImage: DeepLink.third.icon) } .tag(DeepLink.third.tag) } .tint(.black) .onAppear { // Styling the TabBar let appearance = UITabBarAppearance() appearance.backgroundEffect = UIBlurEffect(style: .systemUltraThinMaterial) appearance.backgroundColor = UIColor(.white.opacity(0.6)) UITabBar.appearance().standardAppearance = appearance UITabBar.appearance().scrollEdgeAppearance = appearance } .onOpenURL { url in // handling the deep link print(\u0026#34;URL: \\(url)\u0026#34;) handleIncomingURL(url) } } } private extension ContentView { /// Handles the incoming URL and performs validations before acknowledging. private func handleIncomingURL(_ url: URL) { // Check that the scheme is the one for our app. guard url.scheme == DeepLink.scheme else { return } // Checking the URL contains valid components and saving them in a new variable. guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { print(\u0026#34;Invalid URL\u0026#34;) return } // From the components we are getting the URL host, in our case this equals the actions for our deep links. guard let action = components.host else { print(\u0026#34;No action found\u0026#34;) return } // Setting the data for the given action switch action { case DeepLink.first.action: selectedTab = DeepLink.first.tag case DeepLink.second.action: selectedTab = DeepLink.second.tag case DeepLink.third.action: selectedTab = DeepLink.third.tag // For the 3rd View we can accept data, we are getting all the data from the query\u0026#39;s first element, // we could check for specific variable if needed. if let messageValue = components.queryItems?.first?.value { message = messageValue } default: print(\u0026#34;Invalid action\u0026#34;) } } } #Preview { ContentView() } The focus point of the above code is in the modifier .onOpenURL, we are using it to handle the upcoming URL from the deeplink. To handle this url we are defining a function that extracts the information and sets the appropriate data state for our views.\nWe may want to create a ViewModel for handling the data update and retrieval and make our Views reusable.\nTesting the App To test the deep links we could use different approaches, from using the terminal to third party apps. To keep this simple let\u0026rsquo;s use Safari inside the iOS simulator.\nOpen Safari and in the search bar enter the deeplink URL, we have three valid states:\ndeeplink://first deeplink://second deeplink://third?msg=Hello%20from%20Safari, here the msg query is optional, and you could send any data here. Here is the app running:\nConclusion And that\u0026rsquo;s a wrap on our introduction into iOS deep linking with SwiftUI! We\u0026rsquo;ve covered the ins and outs, from setting up the project to creating views and handling deep links seamlessly. Now armed with this knowledge, we\u0026rsquo;re ready to level up our iOS app game. So, dive in, experiment, and enjoy implementing deep linking for a more engaging user experience! Happy coding!\n","date":"2024-01-13T16:22:55Z","permalink":"/p/an-introductory-example-of-deeplinking-in-ios/","title":"An Introductory Example of Deeplinking in iOS"},{"content":"One of the benefits of using Swift is its excellent performance and low memory footprint. This is a key point when doing backend programming.\nThe future of Swift in Linux looks promising. With Swift 5.9, there will be better error logging, and Apple is developing a new open-source cross-platform testing framework with swift-testing that will replace XCTest and works on Linux too.\nLet\u0026rsquo;s hope this encourages more backend systems to be written in Swift.\nIn this tutorial, we\u0026rsquo;ll explore how to code a backend in Vapor and establish its connection to a simple iOS app. I hope this example serves as a good starting point for learning about the interaction between mobile and backend systems.\nGetting Started: Tech Stack Overview Backend Some mobile developers prefer using auto-managed backend solutions like Firebase and may not pay much attention to the server side. Nevertheless, I believe that understanding the basic programming and concepts used in the backend can enhance your comprehension of the entire system. This understanding enables us to create solutions that are not only more efficient but also better integrated.\nFramework Backend programming is a complex topic with various frameworks in the market. In the Swift world, the most widely used backend framework is Vapor.\nVapor is an excellent solution for building backend systems. It utilizes a non-blocking, event-driven architecture built on top of Apple\u0026rsquo;s SwiftNIO. This offers us great performance, allowing us to create complex systems that scale well.\nKeep in mind that some third-party services may not have a ready-to-use Swift/Vapor API. This can introduce additional complexity when integrating these services.\nDatabase For the database, we are using PostgreSQL. This database is an open-source relational database and offers great performance with a lot of useful features for any needs we could have.\nTo install and run our database, we are going to use Docker with a docker-compose file. This is great because we can start a database in seconds and allows us to replicate our configuration in different environments.\nApp We use SwiftUI with modern concurrency (async/await), implementing a Network layer to manage requests, and follow the MVVM architecture to organize our code.\nThis approach enables us to delve into modern iOS development, providing insights into how all elements interconnect and respond to changes.\nCoding the Backend Installing docker Let\u0026rsquo;s start our project by installing docker, we need it to create the database and the container for our vapor app.\nWe can use Homebrew to install it via the terminal, or just get it from the Docker site.\nIf you decide to use Homebrew, we need to have brew installed and run the following command on the terminal:\nbrew install --cask docker Open Docker and let\u0026rsquo;s continue.\nCreating and running the database To set up our PostgreSQL database, we\u0026rsquo;ll need to create a docker-compose.yml file and define its configuration. Follow these steps:\nCreate a file named docker-compose.yml in your project directory.\nAdd the following configuration to the docker-compose.yml file:\nversion: \u0026#39;3\u0026#39; services: postgres: image: postgres:latest container_name: postgres restart: always volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_DB: postgres POSTGRES_USER: user POSTGRES_PASSWORD: password ports: - \u0026#39;5432:5432\u0026#39; networks: - fullstack-ios-net volumes: postgres_data: networks: fullstack-ios-net: driver: bridge volumes will tell Docker to create the persistance layer with the given name for us, so we don\u0026rsquo;t need to specify any directory.\nOpen a terminal in the directory containing the docker-compose.yml file.\nRun the following command to start the database container:\ndocker-compose -p database-vapor up -d You can change the container name by replacing database-test with your preferred name. The -d flag runs the container in detached mode, freeing up the terminal.\nThis will create a running database locally, ready for us to use in our backend.\nTo confirm that the database is running, execute the command docker ps in your terminal. Alternatively, you can check Docker Desktop if it is installed on your system.\nSince we are running separate containers for the database and the vapor app, we need to get the network in which the database is attached, to do so let\u0026rsquo;s run:\ndocker network ls In my case, the network created for this container is called database-vapor_fullstack-ios-net. We\u0026rsquo;ll use this network for the vapor app, so keeps your network name saved.\nTo check the containers attached to a network run: docker network inspect \u0026lt;network_name/ID\u0026gt;.\nTo delete a network run the command: docker network rm \u0026lt;network_name/ID\u0026gt;.\nHere\u0026rsquo;s an example of the resulting database running on my system:\nCreating the Vapor Container Let\u0026rsquo;s move on to the exciting part – building our Vapor app. For this process, we\u0026rsquo;ll leverage a development container with VSCode, a fantastic tool that helps maintain a clean local environment and ensures reproducibility for every project.\nFeel free to adjust this according to your style and preferences!\nFollow these steps to create your empty vapor container:\nCreate a root directory for the vapor container. Inside the root directory, create a new directory called .devcontainer. Inside .devcontainer, create a new file called devcontainer.json and add the following: { \u0026#34;name\u0026#34;: \u0026#34;Vapor\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;swift:latest\u0026#34;, \u0026#34;runArgs\u0026#34;: [ \u0026#34;--name=vapor-local-devcontainer\u0026#34;, \u0026#34;--network=database-vapor_fullstack-ios-net\u0026#34; ], \u0026#34;postCreateCommand\u0026#34;: \u0026#34;bash .devcontainer/vaporinstall.sh\u0026#34;, \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;extensions\u0026#34;: [ \u0026#34;sswg.swift-lang\u0026#34;, \u0026#34;streetsidesoftware.code-spell-checker\u0026#34; ] } } } We are adding this container to the same network as the database, this will allow communication between the two containers.\nCreate the Vapor installation script vaporinstall.sh file inside the .devcontainer directory. Add the following to vaporinstall.sh: #!/bin/bash echo \u0026#34;Vapor installation is starting\u0026#34; # Updating and installing packages apt update \u0026amp;\u0026amp; apt upgrade -y apt install -y make # Installing Vapor git clone https://github.com/vapor/toolbox.git cd toolbox make install cd .. rm -rf toolbox echo \u0026#34;Vapor installation was successful\u0026#34; Open the project in VSCode and then select the option Reopen in Container. To check Vapor is installed, run in a terminal vapor --version.\nThis will create the container and install vapor inside, you\u0026rsquo;re ready to code the backend in Vapor!\nFinally, let\u0026rsquo;s check that both containers are under the same network, run this command in the host terminal:\ndocker network inspect database-vapor_fullstack-ios-net | grep -A 20 \u0026#34;Containers\u0026#34; There you can check that our two containers are listed in the output under \u0026quot;Containers\u0026quot;, we\u0026rsquo;re good to continue.\nCreating a barebone Vapor App It\u0026rsquo;s time to create our app inside the container, this is a simple task thanks to the integrated vapor tools:\nRun in the container\u0026rsquo;s terminal the following: vapor new temperature-api -n -n will create a barebone project\nEnter the new directory cd temperature-api\nRun the project swift run\nCongratulations, now your Vapor app is up and running. To verify that it works as expected you can check the root endpoint / in the URL mentioned in the terminal, usually http://127.0.0.1:8080/\nCreating the Model for the Database Let\u0026rsquo;s start by codding the Model that will represent our table!\nThis step is crucial because we are modeling our problem and it\u0026rsquo;s important that we discuss this thoroughly to get a model that efficiently defines our data and allow us to have a simple and performant representation of the problem\u0026rsquo;s data.\nThis is a really simple app, so we are just storing a given temperature and a related date. Since this is simple to model we\u0026rsquo;re going to use a simple table containing these values and a unique identifier for each new value.\nInstalling Fluent and Postgres Driver We\u0026rsquo;re going to use an ORM to communicate with our database, this is a great tool that allow us to stay in Swift when making queries, models and relationships. In our case we decided to use Fluent and a Postgres Driver.\nTo install them, follow the next steps:\nOpen Package.swift Add the following lines in the dependencies array: .package(url: \u0026#34;https://github.com/vapor/fluent.git\u0026#34;, from: \u0026#34;4.8.0\u0026#34;), .package(url: \u0026#34;https://github.com/vapor/fluent-postgres-driver.git\u0026#34;, from: \u0026#34;2.8.0\u0026#34;), Add the following inside the second dependencies array, nested inside targets: .product(name: \u0026#34;Fluent\u0026#34;, package: \u0026#34;fluent\u0026#34;), .product(name: \u0026#34;FluentPostgresDriver\u0026#34;, package: \u0026#34;fluent-postgres-driver\u0026#34;), If you\u0026rsquo;re using a different database look into Fluent documentation to find an appropriate driver.\nYour Package.swift should look similar to this:\n// swift-tools-version:5.9 import PackageDescription let package = Package( name: \u0026#34;temperature-logger\u0026#34;, platforms: [ .macOS(.v13) ], dependencies: [ // 💧 A server-side Swift web framework. .package(url: \u0026#34;https://github.com/vapor/vapor.git\u0026#34;, from: \u0026#34;4.83.1\u0026#34;), .package(url: \u0026#34;https://github.com/vapor/fluent.git\u0026#34;, from: \u0026#34;4.8.0\u0026#34;), // added .package(url: \u0026#34;https://github.com/vapor/fluent-postgres-driver.git\u0026#34;, from: \u0026#34;2.8.0\u0026#34;), //added ], targets: [ .executableTarget( name: \u0026#34;App\u0026#34;, dependencies: [ .product(name: \u0026#34;Vapor\u0026#34;, package: \u0026#34;vapor\u0026#34;), .product(name: \u0026#34;Fluent\u0026#34;, package: \u0026#34;fluent\u0026#34;), // added .product(name: \u0026#34;FluentPostgresDriver\u0026#34;, package: \u0026#34;fluent-postgres-driver\u0026#34;) //added ] ), .testTarget(name: \u0026#34;AppTests\u0026#34;, dependencies: [ .target(name: \u0026#34;App\u0026#34;), .product(name: \u0026#34;XCTVapor\u0026#34;, package: \u0026#34;vapor\u0026#34;), // Workaround for https://github.com/apple/swift-package-manager/issues/6940 .product(name: \u0026#34;Vapor\u0026#34;, package: \u0026#34;vapor\u0026#34;), ]) ] ) These new packages will be automatically added to our project.\nCreating our Temperature model The models we define in this step will be used to create the tables in our database.\nCreate a new file called Temperature.swift in the following path: Sources/App/Models/ Add the following code: import Vapor import Fluent final class Temperature: Model, Content { static let schema: String = \u0026#34;temperatures\u0026#34; @ID var id: UUID? @Field(key: \u0026#34;temperature\u0026#34;) var temperature: String @Field(key: \u0026#34;date\u0026#34;) var date: String init() { } init(id: UUID? = nil, temperature: String, date: String) { self.id = id self.temperature = temperature self.date = date } } The property wrappers are defined in Fluent and let us mark the different roles for our database, the names inside @Field are used to represent the name of the columns in the table. The schema at the top will be used as the table name in our database.\nExcellent, we have our schema defined.\nConnecting the Postgres Database to our Vapor App Oru next goal is to connect our database to our vapor app and create the schema for our system.\nCreating the Migration file Migrations serve the purpose of defining and applying changes to the database schema. A migration is essentially a set of instructions that describe how to evolve the database structure, creating or modifying tables and their columns.\nTo create our own migration file follow the next steps:\nCreate the file CreateTemperature.swift in the path: Sources/App/Migrations/ Add the following code: // To run migrations, call `swift run App migrate` from the command line import Fluent struct CreateTemperature: AsyncMigration { func prepare(on database: Database) async throws { try await database.schema(\u0026#34;temperatures\u0026#34;) .id() .field(\u0026#34;temperature\u0026#34;, .string, .required) .field(\u0026#34;date\u0026#34;, .string, .required) .create() } func revert(on database: Database) async throws { try await database.schema(\u0026#34;temperatures\u0026#34;) .delete() } } The prepare function is part of the migration and specifies what changes to make to the database when applying the migration. In simple terms, it says:\nCreate a new table named \u0026rsquo;temperatures'. Add an \u0026lsquo;id\u0026rsquo; column (which is typically an identifier for each row in the table). Add a \u0026rsquo;temperature\u0026rsquo; column of type \u0026lsquo;string\u0026rsquo; that is required. Add a \u0026lsquo;date\u0026rsquo; column of type \u0026lsquo;string\u0026rsquo; that is also required. Ensure that all these changes are applied to the database. The revert function is part of the migration and specifies how to undo the changes made by the prepare function. In simple terms, it says:\nDelete the \u0026rsquo;temperatures\u0026rsquo; table from the database. Setting-up the Postgres Database We need to configure the credentials for our Postgres database. To do this we need to open the file configure.swift inside the path: Sources/App/.\nAdd the packages needed at the top of the file:\nimport Fluent import FluentPostgresDriver Add the following code at the top inside the configure function:\nThe data for these credentials are defined in the docker-file of the database. If you\u0026rsquo;re hosting your database in a system other than localhost, use your URL in place of localhost.\n// defining the postgres configuration object let databaseConfiguration = SQLPostgresConfiguration( hostname: \u0026#34;localhost\u0026#34;, username: \u0026#34;user\u0026#34;, password: \u0026#34;password\u0026#34;, database: \u0026#34;postgres\u0026#34;, tls: .disable ) // applying the configuration app.databases.use( .postgres(configuration: databaseConfiguration), as: .psql ) // Adding our migration object app.migrations.add(CreateTemperature()) // Optional, make the logger more verbose app.logger.logLevel = .debug Your configuration.swift should look similar to this:\nimport Vapor import Fluent import FluentPostgresDriver // configures your application public func configure(_ app: Application) async throws { // uncomment to serve files from /Public folder // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) // defining the postgres configuration object let databaseConfiguration = SQLPostgresConfiguration( hostname: \u0026#34;localhost\u0026#34;, username: \u0026#34;user\u0026#34;, password: \u0026#34;password\u0026#34;, database: \u0026#34;postgres\u0026#34;, tls: .disable ) // applying the configuration app.databases.use( .postgres(configuration: databaseConfiguration), as: .psql ) // Adding our migration object app.migrations.add(CreateTemperature()) // Optional, make the logger more verbose app.logger.logLevel = .debug // register routes try routes(app) } Now stop the app and run the following commands to apply the migrations and get the database connected.\n# Stop the project by pressing CTRL + C swift run App migrate # to start migration Creating the endpoints We\u0026rsquo;re about to finish the backend for our iOS app. This backend is quite simple, it will expose a GET method that will return all the stored temperatures in the database and a post method that will allow us to save a temperature sent from our iOS app.\nSince we are only using two endpoints we can add this logic to the file routes.swift, found in the path temperature-api/Sources/App/.\nOpen the file and add the following modules:\nimport Fluent import FluentPostgresDriver Next, inside the routes function, delete all its body and add this:\napp.get(\u0026#34;temperatures\u0026#34;) { req async throws in app.logger.info(\u0026#34;Temperatures GET received.\u0026#34;) return try await Temperature.query(on: req.db).all() } app.post(\u0026#34;temperatures\u0026#34;) { req async throws -\u0026gt; Temperature in app.logger.info(\u0026#34;Temperature POST request received.\u0026#34;) let temperature = try req.content.decode(Temperature.self) try await temperature.create(on: req.db) return temperature } Your routes.swift file should look like this:\nimport Vapor import Fluent import FluentPostgresDriver func routes(_ app: Application) throws { app.get(\u0026#34;temperatures\u0026#34;) { req async throws in app.logger.info(\u0026#34;Temperatures GET received.\u0026#34;) try await Temperature.query(on: req.db).all() } app.post(\u0026#34;temperatures\u0026#34;) { req async throws -\u0026gt; Temperature in app.logger.info(\u0026#34;Temperature POST request received.\u0026#34;) let temperature = try req.content.decode(Temperature.self) try await temperature.create(on: req.db) return temperature } } GET Route for \u0026ldquo;temperatures\u0026rdquo;:\nWhen the server receives a GET request at the path \u0026quot;/temperatures\u0026quot;, it logs an informational message saying \u0026quot;Temperatures GET received.\u0026quot;. It then attempts to retrieve all temperature records from the database asynchronously using try await Temperature.query(on: req.db).all(). POST Route for \u0026ldquo;temperatures\u0026rdquo;:\nWhen the server receives a POST request at the path \u0026quot;/temperatures\u0026quot;, it logs an informational message saying \u0026quot;Temperature POST request received.\u0026quot;. It then tries to decode the request\u0026rsquo;s content into a Temperature model object using let temperature = try req.content.decode(Temperature.self). After decoding, it attempts to create a new temperature record in the database asynchronously with try await temperature.create(on: req.db). Finally, it returns the created Temperature object as a response. Congratulations, you\u0026rsquo;ve finished your backend using Vapor!\nRun it with swift run and let\u0026rsquo;s test it!\nTesting our backend For testing our backend I\u0026rsquo;m using Postman.\nFirst let\u0026rsquo;s try the GET, since the database is empty it should return an empty array [].\nNow let\u0026rsquo;s test the POST by adding some data.\nFinally, let\u0026rsquo;s check GET again to verify we get the new stored data from the DB.\nGreat, our backend is working as expected.\nLet\u0026rsquo;s continue with the iOS app, great work!\nCoding the iOS App Let\u0026rsquo;s start our iOS app that will connect to our vapor backend.\nOne of the most used design patters for SwiftUI is MVVM, this stands for Model, View and ViewModel.\nModel: Represents the application\u0026rsquo;s data and business logic. It is responsible for managing and manipulating the data, as well as enforcing business rules.\nView: Represents the user interface and is responsible for displaying data to the user. It observes changes in the ViewModel and updates accordingly. In some implementations, the View may also handle user input and pass it to the ViewModel.\nViewModel: Acts as an intermediary between the Model and the View. It contains the presentation logic and transforms the data from the Model into a form that can be easily displayed by the View. The ViewModel often exposes properties and commands that the View can bind to.\nThe key point is that the ViewModel doesn\u0026rsquo;t tell the view what to do; it just receives queries from the view. When the data is ready, it notifies the subscribers, mostly views, about the changes. Then, the views that get the notifications can act accordingly with the new data received.\nLet\u0026rsquo;s create a new iOS SwiftUI project in Xcode to continue, we could name it Temperatures App or anything you like.\nCreating the Model Let\u0026rsquo;s start with the Model, this model would be quite similar to the one in our backend.\nLet\u0026rsquo;s create a new Group called Models and inside this group create a new file called Temperature.swift.\nimport Foundation struct Temperature: Codable, Identifiable { var id: UUID = UUID() var temperature: String = \u0026#34;\u0026#34; var date: String = \u0026#34;\u0026#34; } This is the model our app will use when creating new temperatures locally. The code acts similarly to the one in the backend; we define a new ID for each new temperature and store the temperature and date for that measurement.\nWe use Identifiable to make every temperature object uniquely identifiable. This is useful in the SwiftUI ForEach view.\nThe Codable protocol guarantees that our data can be encoded and decoded, in our case, to and from JSON.\nCreating the Network Layer The network layer is next; this layer will handle the communication with the server.\nCreating the Request Protocol Let\u0026rsquo;s start by defining a protocol that will tell us the public API for a network request. The network request will handle all the information needed by the network service to perform its work.\nCreate a new group called Networking and inside create a new file called NetworkRequestProtocol.swift. Then add this code:\nimport Foundation protocol NetworkRequestProtocol { var url: URL { get } var method: String { get } var headers: [String: String]? { get } var body: Data? { get } } As mentioned before, these are the elements we will need to pass to the network manager to be able to send requests to our backend:\nThe URL of our backend. The type of HTTP method we want to use. The headers for our requests. The body of our request in case of POST. Creating the Temperature Request Now let\u0026rsquo;s conform to this protocol. Create a new file called TemperatureRequest.swift in the Networking group and add the following code:\nimport Foundation enum TemperatureRequest: NetworkRequestProtocol { case post(temperature: Temperature) case get var url: URL { switch self { case .get: URL(string: \u0026#34;http://localhost:8080/temperatures\u0026#34;) ?? URL(string: \u0026#34;https://apple.com\u0026#34;)! case .post: URL(string: \u0026#34;http://localhost:8080/temperatures\u0026#34;) ?? URL(string: \u0026#34;https://apple.com\u0026#34;)! } } var method: String { switch self { case .get: \u0026#34;GET\u0026#34; case .post: \u0026#34;POST\u0026#34; } } // no matter the selected case we always return the same header var headers: [String : String]? { [\u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;] } var body: Data? { switch self { case .get: nil case .post(let temperature): try? JSONEncoder().encode(temperature) } } } We\u0026rsquo;re making this app simple, so we are not defining custom errors.\nenums are great for handling our network request parameters. Here, TemperatureRequest conforms to NetworkRequestProtocol. This is great because we can change our request object with a different one by only conforming to the same protocol. This helps to lower code coupling and improves testability.\nThe code itself configures the GET and POST method data. In the case of POST, we send the Temperature object and convert it into a proper JSON type for our backend. The rest of the code is self-explanatory.\nCreating the Network Service Protocol As mentioned before, protocols are a great way to lower coupling in code and help with testability because we can create mock objects that can simplify our tests for different parts of the code.\nLet\u0026rsquo;s create a new file called NetworkServiceProtocol.swift in the Networking group and add this code:\nimport Foundation protocol NetworkServiceProtocol { func execute(request: NetworkRequestProtocol) async throws -\u0026gt; Data } As before, we are defining the public API for this object. In this case, we only have one function that will execute the request using the request data we pass.\nCreating the Network Service The Network service is the final component we need to implement in our Network layer. This object is responsible for doing the actual communication with the backend. To configure our request, we pass a TemperatureRequest object with the parameters we need.\nIn the same Networking group, create a new file called NetworkService and add the following code:\nimport Foundation struct NetworkService: NetworkServiceProtocol { func execute(request: NetworkRequestProtocol) async throws -\u0026gt; Data { // configuring the request var urlRequest = URLRequest(url: request.url) urlRequest.httpMethod = request.method urlRequest.allHTTPHeaderFields = request.headers urlRequest.httpBody = request.body // performing the request, we wait for the server response let (data, _) = try await URLSession.shared.data(for: urlRequest) // returning the data from the server return data } } In simpler terms the code above does the following:\nImplementing the protocol NetworkServiceProtocol execute function: takes a NetworkRequestProtocol as a parameter and performs a network request to our backend. URLRequest setup: Inside the execute function, a URLRequest is created using information from the provided NetworkRequestProtocol, such as the URL, HTTP method, headers, and request body. Network request: The function then uses URLSession.shared to perform an asynchronous network request based on the constructed URLRequest. It awaits the response and extracts the obtained data. Return data: Finally, the obtained data is returned from the function. Notice that this layer is not responsible of converting the json data to the proper type for our app.\nCreating the View Model To create the View Model, we need to create a new group called ViewModels and inside this new group create a new file called TemperaturesViewModel.swift. Add the following code to this file:\nimport Foundation @MainActor // Runs this code in the main thread to avoid updating the UI outside of it. final class TemperatureViewModel: ObservableObject { @Published private(set) var temperatures: [Temperature] private let networkService: NetworkServiceProtocol // Injecting dependencies init(temperatures: [Temperature] = [], networkService: NetworkServiceProtocol) { self.temperatures = temperatures self.networkService = networkService } /// POST a new temperature to the backend. func addTemperature(temperature: String, date: String) async { let newTemperature = Temperature(temperature: temperature, date: date) let request = TemperatureRequest.post(temperature: newTemperature) do { let _ = try await networkService.execute(request: request) } catch { print(\u0026#34;Error adding temperature\u0026#34;) } } /// GET all stored temperatures in the backend. func getTemperatures() async { let request = TemperatureRequest.get // perform request do { let data = try await networkService.execute(request: request) // decode data guard let decodedData = decodeData(data: data) else { return } print(decodedData) // saving data on published var temperatures = decodedData } catch { print(\u0026#34;Some error in get\u0026#34;) } } } // MARK: - Helper private functions private extension TemperatureViewModel { func decodeData(data: Data) -\u0026gt; [Temperature]? { do { return try JSONDecoder().decode([Temperature].self, from: data) } catch { print(\u0026#34;Error decoding temperatures\u0026#34;) return nil } } } This code may seem complex, but in reality, it is just doing two things: sending data back to the server and getting data from the server. This happens in the methods addTemperature and getTemperatures.\nThen we are publishing the values with @Published private(set) var temperatures: [Temperature], so any object that subscribes to updates can get a notification for changes in this variable. Notice the private(set), this allows read-only access to the value.\nAll of this is handled in the main thread to avoid updating the UI in a different thread by marking the object with @MainActor.\naddTemperature function: This function is responsible for sending a new temperature to the backend using a POST request:\nIt creates a new Temperature object with the provided temperature and date. It creates a TemperatureRequest for a POST request, encapsulating the new temperature data. It tries to execute the request asynchronously using the networkService. If an error occurs during the request execution, it catches the error and prints an error message. getTemperatures function: This function is responsible for fetching all stored temperatures from the backend using a GET request:\nIt creates a TemperatureRequest for a GET request. It tries to execute the request asynchronously using the networkService. If the request is successful, it decodes the received data into an array of Temperature objects using the decodeData function. It prints the decoded data and assigns it to the temperatures property, which is likely a @Published property that updates the UI when changed. If an error occurs during the request execution, it catches the error and prints an error message. Creating the Views Now it\u0026rsquo;s time to see something on our phone\u0026rsquo;s screen. Let\u0026rsquo;s create a new group called Views. Here, we are going to add all the views for our app.\nThe system will have two views: one for creating new temps and the other for viewing the current temperatures stored in our system. To manage the navigation of these two views, we are going to use a tab navigation style. Since the tab view doesn\u0026rsquo;t have a title, we\u0026rsquo;re going to use a title view that will help us to name our screens. This also allows us to customize the screens to our liking.\nSo, let\u0026rsquo;s create these three SwiftUI files inside Views. Let\u0026rsquo;s call them:\nRootView.swift: Our tab view will be here. PostTemperaturesView.swift: It will display a simple UI to send the data to the server. GetTemperaturesView.swift: It will display a simple table with the temperatures from the server. TitleView.swift: A helper view that will display the title of the current view. Creating TitleView The easiest view for this app is the TitleView. It will be a simple view that displays formatted text on the screen.\nAdd this code to TitleView.swift:\nimport SwiftUI struct TitleView: View { let title: String var body: some View { Text(title) .font(.largeTitle) .fontWeight(.bold) .padding() } } #Preview(traits: .sizeThatFitsLayout) { TitleView(title: \u0026#34;Custom Title\u0026#34;) } In this view we are just applying some font styles to the given title.\nCreating PostTemperaturesView This view will present the user with a button and two fields for the temperature and date. We are implementing some extremely basic validation that will disable the button if both text fields are empty. In a production app, we should validate against edge cases and be more thoughtful.\nAdd the following code to PostTemperaturesView.swift:\nimport SwiftUI struct PostTemperaturesView: View { @State private var temperature = Temperature() @EnvironmentObject private var temperatureViewModel: TemperatureViewModel var body: some View { ZStack { VStack { TitleView(title: \u0026#34;Post your temperature\u0026#34;) Divider() Spacer() } VStack { inputSection() .padding() buttonSection() } } } } #Preview { PostTemperaturesView() } private extension PostTemperaturesView { /// Checks if the temperature components are empty or not. var invalidTemperatureData: Bool { temperature.date.isEmpty || temperature.temperature.isEmpty ? true : false } /// Based on the validity of the temperature object, it returns a specific color. var buttonBackground: Color { invalidTemperatureData ? .gray : .red } func inputSection() -\u0026gt; some View { VStack(spacing: 20) { TextField(\u0026#34;Temperature\u0026#34;, text: $temperature.temperature) .padding() .background(.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 15.0)) TextField(\u0026#34;Date\u0026#34;, text: $temperature.date) .padding() .background(.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 15.0)) } .frame(width: 130, height: 120) .padding() .overlay( RoundedRectangle(cornerRadius: 25.0) .stroke(.red, lineWidth: 3) ) } func buttonSection() -\u0026gt; some View { Button { Task { await temperatureViewModel.addTemperature( temperature: temperature.temperature, date: temperature.date ) } } label: { Text(\u0026#34;POST Temperature\u0026#34;) .foregroundStyle(.white) .frame(maxWidth: .infinity, maxHeight: .infinity) } .disabled(invalidTemperatureData) .frame(width: 230, height: 60) .background(buttonBackground) .clipShape(RoundedRectangle(cornerRadius: 25.0)) } } Since we are not reusing any of the sub-views, I decided to implement them in private functions. This helps to lower the code complexity since all sub-views will have direct access to the parent view properties.\nTo call async functions in our views, we need to create an asynchronous context. To do that, we use Task { ... }. Inside a task, we can call our async functions from the view model. The main advantage of this is that we don\u0026rsquo;t block our UI by waiting for the response.\nThe rest of the code is self-descriptive thanks to the declarative aspect of SwiftUI.\nCreating GetTemperaturesView Since we need to get the current data from the server, I\u0026rsquo;m attaching a .task action that executes every time the view is rendered. This will download the most up-to-date data from the server. Then we will display this information in a formatted table.\nIn production apps, it\u0026rsquo;s important to manage possible errors and inform the users visually about the current state of the task. For this small app, we are not managing that aspect.\nimport SwiftUI struct GetTemperaturesView: View { @EnvironmentObject private var temperatureViewModel: TemperatureViewModel var body: some View { VStack { TitleView(title: \u0026#34;Recorded Temperatures\u0026#34;) Divider() List { ForEach(temperatureViewModel.temperatures) { temp in row(temperature: temp) } } .listStyle(.plain) .task { await temperatureViewModel.getTemperatures() } } } } #Preview { GetTemperaturesView() .environmentObject(TemperatureViewModel(networkService: NetworkService())) // needed for the preview not to crash } private extension GetTemperaturesView { func row(temperature: Temperature) -\u0026gt; some View { VStack { Text(\u0026#34;Date: \\(temperature.date)\u0026#34;) Text(\u0026#34;Temperature: \\(temperature.temperature) \\u{2103}\u0026#34;) } } } As before, thanks to the declarative nature of SwiftUI, the code is self-explanatory.\nCreating RootView Finally, let\u0026rsquo;s piece together all the views and complete our UI.\nThis last part is quite simple to implement, thanks to the way SwiftUI works. Add the following code to the file RootView.swift:\nimport SwiftUI struct RootView: View { var body: some View { TabView { PostTemperaturesView() .tabItem { Label(\u0026#34;POST\u0026#34;, systemImage: \u0026#34;square.and.arrow.up\u0026#34;) } GetTemperaturesView() .tabItem { Label(\u0026#34;GET\u0026#34;, systemImage: \u0026#34;square.and.arrow.down\u0026#34;) } } .tint(.red) // change button color in tab } } #Preview { RootView() .environmentObject(TemperatureViewModel( networkService: NetworkService())) } Here we are creating the tab view and assigning the post and get views we created early in this tutorial.\nConfiguring the Environment Object You may have noticed that some of our views need an Environment object. In the preview, the app works well, but if you try to run it, the app will crash because we are only injecting this object into the preview context. Let\u0026rsquo;s fix that now.\nOpen the entry point app file. It\u0026rsquo;s the one named as your app and contains the attribute @main.\nInside the struct, let\u0026rsquo;s add the view model we will be observing:\n@StateObject private var temperatureViewModel = TemperatureViewModel(networkService: NetworkService()) Next let\u0026rsquo;s change our View entry point to the RootView we just created, and inject the environment object. This will allow access to the ViewModel to RootView and all of its sub-views.\nWe\u0026rsquo;re also attaching an initial get to our backend, this will get us an up to date temperatures each time our app starts.\nWindowGroup { RootView() // change to our RootView .environmentObject(temperatureViewModel) // inject the ViewModel .task { // When open the app, perform an initial GET to the server. await temperatureViewModel.getTemperatures() } } Your file should look like something like this, (your struct may have a different name):\nimport SwiftUI @main struct temperature_logger_App: App { @StateObject private var temperatureViewModel = TemperatureViewModel(networkService: NetworkService()) var body: some Scene { WindowGroup { RootView() .environmentObject(temperatureViewModel) .task { await temperatureViewModel.getTemperatures() } } } } Congratulations! You just finished the app!\nTesting the App To test the app, you need to have the Vapor app and the database containers running. Verify that they are up and running.\nNow, let\u0026rsquo;s run the app on the simulator and add some temperatures. Then, check the list to see how they update in real-time!\nYou can also view the logs in our Vapor app to see the requests we are receiving from our app.\nConclusion In concluding our exploration of building a full-stack iOS application, the versatility of Swift has truly shone through. Swift not only excels in crafting engaging iOS applications but also seamlessly extends its capabilities to backend development, as demonstrated by the integration with the Vapor framework.\nThe collaboration between the iOS app and the Vapor backend illustrates the straightforward nature of Swift in the development landscape. The use of Docker for containerization further streamlined the setup, enabling a hassle-free configuration of a PostgreSQL database synchronized with the Vapor app. This amalgamation of Swift, Docker, and Vapor reflects the smooth interplay of modern development tools.\nThis experience emphasizes Swift\u0026rsquo;s adaptability, not just in the iOS environment but also in Linux setups, where it effortlessly integrates and collaborates with container technologies like Docker. Swift emerges as a pragmatic choice for end-to-end development.\nAs we wrap up the journey of creating a full-stack iOS application, it\u0026rsquo;s worth acknowledging Swift\u0026rsquo;s role in orchestrating various technologies. It showcases how Swift fosters a developer-friendly environment, encouraging innovation and blurring the lines between frontend and backend development. Here\u0026rsquo;s to the practicality of Swift and the possibilities it unlocks in the ever-evolving landscape of software development!\n","date":"2024-01-09T18:04:28-08:00","permalink":"/p/a-simple-full-stack-ios-app-example/","title":"A Simple Full-Stack iOS App Example"},{"content":"One common challenge encountered during frontend development is the persistence of cached data in web browsers.\nWhile browser caches efficiently save time by storing assets and eliminating the need to download them on every website visit, they can pose problems during development when changes are made to the code.\nHow to Force a Full Reload in Safari with All Assets In Safari, resolving this issue is a straightforward process.\nEnsure that Developer Tools are enabled by pressing CMD + ,. Next, navigate to the Advanced tab, and at the bottom of the window, select Show features for web developers. Now you can close this settings window.\nTo perform a full reload, simply press CMD + OPT + R (Safari 11+). This action reloads the current page, bypassing any cached data present in Safari.\nConclusion By incorporating this simple tip into our workflow, we can enhance efficiency in frontend development, particularly when dealing with assets.\nIt can be frustrating not to see updated images, leading us to question our code. However, in many cases, it\u0026rsquo;s just the cache doing its job.\nI trust this brief post proves helpful in resolving such issues.\nUntil next time!\n","date":"2023-12-22T01:40:53Z","permalink":"/p/fully-reload-websites-in-safari/","title":"Fully Reload Websites in Safari"},{"content":"The other day, I encountered an issue involving three directories, and two of them contained a subdirectory with the same name (target).\nMy goal is to delete the target subdirectories in all places with a single command, and here\u0026rsquo;s the solution I found.\nUsing the terminal to delete multiple directories. There is an useful command called find, this command is used to traverse nested directories and evaluate an expression for each element.\nWe can use find to delete files or directories, here is the command:\nfind . -type d -name \u0026#34;target\u0026#34; -exec rm -rf {} + Understanding the command This command is easy to grasp, first we have ., this is telling find to start traversing in the current directory.\nNext we have -type, this argument allows to select a a specific valid type.\nThe valid types are listed below:\nb: block special c: character special d: directory f: regular file l: symbolic link p: FIFO s: socket Since we need to find an specific directory we use d.\nNext it\u0026rsquo;s -name, we need to give the string that contains the name we are searching for. In our case we are looking for the directories named \u0026quot;target\u0026quot;.\nThe last part of the command, -exec, is telling what to do once we find a valid element. In this case we want to delete all the found occurrences, so we\u0026rsquo;re passing rm -rf.\nThe {} is just telling that we need to use the current element\u0026rsquo;s name, and + is used to terminate the -exec part.\nConclusion By using this simple command we can find and delete subdirectories in an easy and convenient way, which I find to be faster than using the GUI.\nI hope this simple tutorial can help you to solve these kind of problems.\n","date":"2023-12-22T01:13:31Z","permalink":"/p/deleting-duplicates-subdirectories-and-content/","title":"Deleting Duplicates Subdirectories and Content"},{"content":"Let\u0026rsquo;s continue our exploration of the wonders of local development with containers.\nThis time, we\u0026rsquo;ll delve into the world of Haskell programming. Instead of burdening your local environment with the entire toolchain, we\u0026rsquo;ll opt for a more streamlined approach: creating a dev container. This example will illustrate how effortlessly you can integrate new tools and programming languages without causing any disruptions to your local system.\nWhat is Haskell Haskell, a pure functional programming language, though not as commonly used in the industry, is a powerful language whose fundamentals can significantly enhance your programming skills.\nHowever, Haskell comes with a complex toolchain and numerous dependencies, potentially posing challenges for some systems. If you\u0026rsquo;re not a professional Haskell developer, the process of installing all these tools may not be worthwhile.\nThis is where dev containers come to the rescue, allowing you to sidestep these issues while still enabling you to experiment with Haskell. Let\u0026rsquo;s get started.\nPersonally, I find Haskell to be a fascinating language that offers valuable insights into novel techniques and encourages a different perspective on problem-solving. Experimenting with Haskell has been particularly enjoyable for me, providing a unique opportunity to broaden my understanding and approach to coding challenges.\nCreating the Haskell dev container As usual, we start by creating our local directory with a child directory called .devcontainer, inside this dir let\u0026rsquo;s create a new file called devcontainer.json, then add this code.\n{ \u0026#34;name\u0026#34;: \u0026#34;Haskell Base\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;mcr.microsoft.com/devcontainers/base:ubuntu\u0026#34;, \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;extensions\u0026#34;: [ \u0026#34;haskell.haskell\u0026#34;, \u0026#34;visualstudioexptteam.vscodeintellicode\u0026#34;, \u0026#34;visualstudioexptteam.intellicode-api-usage-examples\u0026#34;, \u0026#34;streetsidesoftware.code-spell-checker\u0026#34; ] } }, \u0026#34;postCreateCommand\u0026#34;: \u0026#34;zsh .devcontainer/installation.sh\u0026#34;, \u0026#34;runArgs\u0026#34;: [\u0026#34;--name\u0026#34;, \u0026#34;haskell_devcontainer\u0026#34;] } Let\u0026rsquo;s break down its key elements:\nThe name field is set to \u0026ldquo;Haskell Base,\u0026rdquo; providing a recognizable identifier for this development environment.\nThe image field points to the Docker image \u0026ldquo;mcr.microsoft.com/devcontainers/base:ubuntu.\u0026rdquo; This image serves as the foundation for the development environment.\nWithin the customizations section, various Visual Studio Code extensions are specified. These extensions enhance the Haskell development experience.\nThe postCreateCommand field designates a post-creation command that runs after the dev container is set up.\nFinally, the runArgs field supplies extra arguments for the docker run command when initiating the dev container. It assigns the name \u0026ldquo;haskell_devcontainer\u0026rdquo; to the container for easy identification.\nAs you may have noticed, we need to create a script to install Haskell in our container. To craft this file, we follow the instructions provided by the Haskell page. This shows us how we can easily configure our container as if it were a standard local installation. This shows the power and simplicity of working with local containers.\nWithin the .devcontainer directory, create a new file named configuration.sh and insert the following code into this newly created file.\n#!/bin/zsh sudo apt update sudo apt install -y build-essential curl libffi-dev libffi8ubuntu1 libgmp-dev libgmp10 libncurses-dev libncurses5 libtinfo5 curl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh echo \u0026#39;export PATH=\u0026#34;$PATH:$HOME/.ghcup/bin\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.zshrc source ~/.zshrc echo \u0026#39;export PATH=\u0026#34;$PATH:$HOME/.ghcup/bin\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc echo \u0026#34;IMPORTANT: If bash doesn\u0026#39;t find cmds, run: source ~/.bashrc\u0026#34; In this script, we\u0026rsquo;re simply adhering to the installation recommendations provided by the official Haskell documentation for Linux. no extra steps or magic is needed for the container; we can treat it just like a local installation.\nAfter preparing this, open your project in a dev container, go through the setup process (simply respond with \u0026lsquo;Y\u0026rsquo; to all questions), and you\u0026rsquo;re all set to delve into Haskell on your machine.\nTesting Haskell Let\u0026rsquo;s create a simple program to test our envirment. Create a new file called \u0026ldquo;main.hs\u0026rdquo; and add the following code.\nmain = do putStrLn \u0026#34;What is 2 + 2?\u0026#34; x \u0026lt;- readLn if x == 4 then putStrLn \u0026#34;You\u0026#39;re right!\u0026#34; else putStrLn \u0026#34;You\u0026#39;re wrong\u0026#34; Now let\u0026rsquo;s run it by importing it to the interpreter\nghci main.hs The ghci REPEL will open, now let\u0026rsquo;s run our function by just calling its name inside ghci\nmain This will ask you for an input and will print the result\nWhat is 2 + 2? 4 You\u0026#39;re right! Congratulation, your Haskell environment is working correctly!\nConclusion In this exploration of local development with containers, we\u0026rsquo;ve ventured into Haskell programming, sidestepping the complexities of the language\u0026rsquo;s toolchain by creating a streamlined dev container. Despite Haskell\u0026rsquo;s relative industry underuse, its power for skill enhancement remains evident.\nDev containers prove invaluable, allowing us to experiment freely with Haskell without the hassle of extensive toolchain installations. Crafting a Haskell dev container is straightforward, following the official documentation to seamlessly configure it as a standard local installation.\nOur simple program confirms that our setup is working correctly. With this, your Haskell environment is ready for exploration. Happy coding!\n","date":"2023-12-21T11:43:00Z","permalink":"/p/containerize-haskell/","title":"Containerize Haskell"},{"content":"Hey there! Today, let\u0026rsquo;s dive back into the world of LeetCode problems with Swift.\nAfter successfully conquering the Two Sum problem, we\u0026rsquo;re geared up to take on the next challenge: Two Sum II.\nTo make things interesting, let\u0026rsquo;s explore this problem using a two pointers technique. Ready to tackle it together?\nProblem We are given an array of numbers sorted in non-decreasing order (this means each element is greater or equal to the preceding one) and a target value.\nWe are asked to find two numbers in the array such that the sum of them is equal to the target value and return those indices.\nHere is an example:\nInput: numbers = [2,7,11,15], target = 9 Output: [1,2] Note: The index count specified in the problem starts at 1, not 0.\nIn this case, the values at indices 1 and 2 sum up to 9. Therefore, the expected output is the array [1,2] representing these indices.\nNote: There is always exactly one solution, elements cannot be repeated, and the solution must be achieved using constant extra space.\nSolution For my solution, I opted to leverage the ordered nature of the list by employing a two-pointer approach. With the assurance that the list is already sorted, I establish two pointers. These pointers dynamically adjust based on whether the sum of the elements they point to is smaller or greater than the target. This strategy proves effective in navigating the sorted array efficiently.\nfunc twoSum(_ numbers: [Int], _ target: Int) -\u0026gt; [Int] { var left = 0 // pointer at the beginning of the list var right = numbers.count - 1 // pointer at the end of the list // iterating till both pointers are side by side while left \u0026lt; right { let sum = numbers[left] + numbers[right] if sum \u0026gt; target { // since sum is bigger than target we need to make the sum smaller right -= 1 } else if sum \u0026lt; target { // since sum is smaller than target we need to make the sum bigger left += 1 } else { // we found the two indices, return them return [left + 1, right + 1] } } // no indices found, return nothing return [] } This code efficiently identifies the two indices by employing two pointers initialized at the end of the list. Leveraging the sorted nature of the list, the pointers are strategically moved as needed until a valid solution is found.\nThe code is well-commented to elucidate each step, and given its straightforward nature, additional explanation may not be necessary.\nConclusion Wrapping up our exploration of the Two Sum II problem in Swift, we utilized a handy technique known as \u0026rsquo;two pointers.\u0026rsquo; The inherent order of the list allowed us to smartly navigate through elements, efficiently narrowing down potential pairs that add up to the target value.\nSwift\u0026rsquo;s simplicity and expressiveness played well with this approach, allowing us to implement a clean and concise solution. The language\u0026rsquo;s array handling, combined with the straightforward syntax, offered a seamless experience in dealing with the intricacies of the problem.\nNo need for any Swift promotional banner here—just appreciating how the language\u0026rsquo;s features naturally complemented our strategy. Swift or not, exploring these algorithmic challenges keeps our coding skills sharp and our problem-solving toolkit diverse. Ready for the next challenge?\n","date":"2023-12-19T09:05:39Z","permalink":"/p/leetcode-two-sum-ii/","title":"LeetCode –  Two Sum II"},{"content":"Hello devs,\nI\u0026rsquo;ve recently been exploring the synergy between Docker and VSCode for local development. The concept of consolidating code and dependencies into containers has caught my attention. This approach not only establishes a clean slate for each project but also promotes a stateless system, a goal that aligns seamlessly with my coding environment.\nTo explore these ideas further, let\u0026rsquo;s create a small project: containerizing an installation of Hugo. This way, we can run our own dev blog anywhere without worrying about configuration and compatibility issues. It sounds great, so let\u0026rsquo;s dive into the code and make it happen!\nWhat are Dev Containers? Dev containers, a feature in VSCode, empower us to use local containers for programming, seamlessly configured with both our dependencies and code. This operates under the hood, allowing us to open our projects within these containers and easily push changes to platforms like GitHub.\nIn essence, we work as if we were coding locally, avoiding any impact on our real system. Once our work is complete, we can delete these containers from our system without any repercussions. The magic doesn\u0026rsquo;t stop there—having a reproducible environment enhances teamwork among developers. Now, we can ensure the same environment, regardless of the system in use, eliminating the classic \u0026ldquo;it works on my machine\u0026rdquo; scenario.\nIt\u0026rsquo;s truly magical.\nPrerequisites To make this project, we only need two tools installed in our system:\nDocker (local or remote works, in this tutorial we assume it\u0026rsquo;s installed locally) Visual Studio Code I assume you\u0026rsquo;re familiar with containers, how Docker operates, and have a basic understanding of using Hugo\nCreating the Initial Configuration Let\u0026rsquo;s kick things off by establishing the working directory for our Hugo blog. Just as we would with any other project, we create a directory to store our code. This directory can be uploaded to GitHub, and you can manage it with Git as usual.\nmkdir hugo_blog code hugo_blog # opens our project in VSCode; if this doesn\u0026#39;t work, add the \u0026#39;code\u0026#39; command to your system Once our directory is set up, let\u0026rsquo;s initialize Git to track our changes.\ngit init Creating the Configuration for Our Dev Container After preparing our base project, let\u0026rsquo;s set up the configuration files for dev containers. These files will be stored in a hidden directory named .devcontainer. Let\u0026rsquo;s start by creating it.\n# Inside our project directory mkdir .devcontainer Now, let\u0026rsquo;s generate the JSON file that VSCode will utilize to configure and create the container for us. This file must be named devcontainer.json and should reside within the .devcontainer directory.\nConfiguring the Creation of the Container with Hugo Now, let\u0026rsquo;s dive into the exciting part: configuring the container creation process with the Hugo toolchain. This step automates the installation of all the necessary components for our project. By doing this, we eliminate the need to worry about configuration and can seamlessly dive into our actual work.\nTo begin, let\u0026rsquo;s select a base image for our container. No need to fret about writing any docker-compose or Dockerfile. Everything is taken care of by the dev container.\nMicrosoft already offers several base images for our dev containers, let\u0026rsquo;s use their image for our project.\nWith devcontainer.json, we can specify the VSCode extensions we want installed in our container. Given that we are configuring a Hugo blog, let\u0026rsquo;s include a spell checker extension.\nI\u0026rsquo;ve opted for alpine, a compact Linux system ideal for containers due to its small memory footprint. Now, let\u0026rsquo;s incorporate this configuration into our devcontainer.json.\n{ // The name for our Dev Container; note that this is not the actual Docker container name \u0026#34;name\u0026#34;: \u0026#34;Entangled Dev Blog\u0026#34;, // We can use any other image as a base, but let\u0026#39;s stick with Alpine for this tutorial \u0026#34;image\u0026#34;: \u0026#34;mcr.microsoft.com/devcontainers/base:alpine\u0026#34;, // Configure tool-specific properties. \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;extensions\u0026#34;: [\u0026#34;streetsidesoftware.code-spell-checker\u0026#34;] } }, // runArgs can execute various commands; here, we use --name to assign a name to our Docker container \u0026#34;runArgs\u0026#34;: [\u0026#34;--name\u0026#34;, \u0026#34;entangleddev_devcontainer\u0026#34;], } Great job! Now, to run and work in our dev container, make sure you have the ms-vscode-remote.remote-containers extension installed. Once installed, click on the small \u0026gt;\u0026lt; icon at the bottom left of VSCode and select Reopen in container.\nWhile this approach works, setting up everything manually each time can be tedious. Let\u0026rsquo;s add some automation to streamline the process. Additionally, we don\u0026rsquo;t have Hugo installed yet, so there\u0026rsquo;s a bit more work to be done.\nAt this point, consider pushing your changes to GitHub. Now, every time you clone your project and open it with VSCode, you can replicate this environment on any computer with Docker.\nInstalling Hugo Our devcontainer.json still has some cool tricks up its sleeve—it can run commands once the container is created. This comes in handy for installing the needed dependencies and configuring our environment. To achieve this, we use \u0026quot;postCreateCommand\u0026quot;. Unfortunately, this command only accepts a string with all the commands we want to execute in the terminal. Therefore, we\u0026rsquo;ll create a script to handle all the necessary configurations for us.\nBefore proceeding, we need to incorporate instructions in our devcontainer.json that will run once the container is created. To do this, replace the existing code in our JSON file with the following updated version:\n{ // The name for our Dev Container; note that this is not the actual Docker container name \u0026#34;name\u0026#34;: \u0026#34;Entangled Dev Blog\u0026#34;, // We can use any other image as a base, but let\u0026#39;s stick with Alpine for this tutorial \u0026#34;image\u0026#34;: \u0026#34;mcr.microsoft.com/devcontainers/base:alpine\u0026#34;, // Configure tool-specific properties. \u0026#34;customizations\u0026#34;: { \u0026#34;vscode\u0026#34;: { \u0026#34;extensions\u0026#34;: [\u0026#34;streetsidesoftware.code-spell-checker\u0026#34;] } }, // runArgs can execute various commands; here, we use --name to assign a name to our Docker container \u0026#34;runArgs\u0026#34;: [\u0026#34;--name\u0026#34;, \u0026#34;entangleddev_devcontainer\u0026#34;], // Use \u0026#39;postCreateCommand\u0026#39; to run commands after the container is created. \u0026#34;postCreateCommand\u0026#34;: \u0026#34;ash .devcontainer/configuration.sh\u0026#34; } To install Hugo on Linux, we have several options. For Ubuntu, we could use a Snap (though it might not work inside containers), but we\u0026rsquo;ll opt for getting a precompiled program from its official GitHub page and configuring it. We\u0026rsquo;ll be going with the latter.\nLet\u0026rsquo;s start by creating a shell script to handle our configuration. Within the .devcontainer directory, create a new file named configuration.sh and insert the following code:\n#!/bin/ash echo \u0026#34;==\u0026gt; Starting container post-installation\u0026#34; sudo apk update sudo apk add --no-cache python3 py3-pip wget git tar pip3 install requests python3 .devcontainer/download.py echo \u0026#34;==\u0026gt; Finished container post-installation\u0026#34; Here\u0026rsquo;s a breakdown of what the code accomplishes:\nAlpine employs apk instead of apt. Given Alpine\u0026rsquo;s minimalist nature, we include the necessary dependencies for our container. To simplify data retrieval from the GitHub server, we\u0026rsquo;ll create a Python script named download.py. Continuing, let\u0026rsquo;s create our Python script that will handle the download and unpacking of Hugo. Within the .devcontainer directory, create a new file name download.py and insert the following code:\nimport json import requests import os class HugoInstaller: def __init__(self): self.response = None self.asset_url = \u0026#34;\u0026#34; self.keyword = \u0026#34;arm64.tar.gz\u0026#34; self.package_name = \u0026#34;\u0026#34; self.dir = \u0026#34;/usr/local/bin/hugo_data\u0026#34; def start_installation(self): self._configure_git() self._download_hugo() self._unzip_installer() self._install_hugo_theme() def _configure_git(self): os.system(\u0026#39;git config --global user.name \u0026#34;Docker Container\u0026#34;\u0026#39;) os.system(\u0026#39;git config --global user.email \u0026#34;docker-container@local.com\u0026#34;\u0026#39;) def _download_hugo(self): url = \u0026#34;https://api.github.com/repos/gohugoio/hugo/releases/latest\u0026#34; self.response = requests.get(url) if self.response.status_code == 200: json_data = self.response.json() assets = json_data[\u0026#34;assets\u0026#34;] self.asset_url = \u0026#34;\u0026#34; for asset in assets: file_name = asset[\u0026#34;name\u0026#34;] if self.keyword in file_name: self.asset_url = asset[\u0026#34;browser_download_url\u0026#34;] self.package_name = asset[\u0026#34;name\u0026#34;] break os.system(f\u0026#34;sudo mkdir {self.dir}\u0026#34;) os.system(f\u0026#34;sudo wget -P {self.dir} {self.asset_url}\u0026#34;) else: print(f\u0026#34;Failed to fetch data. Status code: {self.response.status_code}\u0026#34;) def _install_hugo_theme(self): \u0026#39;\u0026#39;\u0026#39;Skip this function if you don\u0026#39;t have a theme selected for your Hugo site\u0026#39;\u0026#39;\u0026#39; os.system(\u0026#34;sudo git submodule update --init --recursive\u0026#34;) def _unzip_installer(self): hugo_file_path = f\u0026#34;{self.dir}/{self.package_name}\u0026#34; os.system(f\u0026#34;sudo tar -xzvf {hugo_file_path} -C {self.dir}\u0026#34;) os.system(f\u0026#34;sudo mv {self.dir}/hugo /usr/local/bin\u0026#34;) os.system(f\u0026#34;sudo rm -r {self.dir}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: installer = HugoInstaller() installer.start_installation() Here\u0026rsquo;s a breakdown of what the code accomplishes:\nHugoInstaller Class Initialization:\nInitializes the HugoInstaller class with default values for properties like response, asset_url, keyword, package_name, and dir. start_installation Method:\nCalls private methods in sequence to initiate the installation process. _configure_git Method:\nConfigures global Git settings within the container to set user name and email. _download_hugo Method:\nFetches the latest release information for Hugo from the GitHub API. Parses the JSON response to extract the URL of the Hugo release asset containing the keyword \u0026ldquo;arm64.tar.gz.\u0026rdquo; Creates a directory (/usr/local/bin/hugo_data) for storing downloaded Hugo assets. Downloads the Hugo release asset and saves it in the designated directory. _install_hugo_theme Method: (warning: call this method with your own theme or skip it altogether)\nInitializes and updates Git submodules, specifically used for installing Hugo themes. _unzip_installer Method:\nExtracts the downloaded Hugo release asset from its tar.gz format. Moves the extracted hugo executable to /usr/local/bin for system-wide access. Removes the temporary directory used for the installation process. __main__ Block:\nInstantiates the HugoInstaller class. Invokes the start_installation method to commence the installation process. Now rebuild your container, and Hugo will be installed. Now, you can start working on your blog inside a container. Commit your changes, and you\u0026rsquo;re done.\nPhew, this was a bit of code, but now you\u0026rsquo;ve containerized your blog with Hugo. You can clone and start your dev container on any machine you want and begin writing about your awesome development journey.\nMore Resources To get more pre-configured images for your dev containers you can check out this GitHub repo by Microsoft here.\nConclusion This straightforward example serves as a foundation for creating your custom development environment tailored to different purposes. The versatility extends to running docker-compose files for more intricate configurations.\nI firmly believe that this stateless approach to local programming will gain increasing significance in the future. It addresses common issues encountered in team collaborations and personal projects by providing a clean system free from pollution. This enables experimentation with diverse tools and languages without compromising our actual systems.\nWhile it\u0026rsquo;s worth noting a potential drawback of being tied to using VSCode, given its widespread adoption, most developers may find this limitation negligible compared to the considerable benefits.\nI hope this inspires you to reconsider your approach to the local programming environment, offering new ways to enhance efficiency and safety in your work or hobby.\nThanks for reading, and happy coding!\n","date":"2023-12-18T09:00:30Z","permalink":"/p/programming-inside-a-developer-container/","title":"Programming inside a Developer Container"},{"content":"Today, I\u0026rsquo;d like to share a simple trick for comparing the states of files using both the terminal and VSCode.\nComparing files Typically, when we need to check for differences between two files, we can use git diff if the files are within a Git repository or simply diff if the files are local.\nHowever, these commands lack a user-friendly interface for comparing files, which is where VSCode\u0026rsquo;s file comparison feature comes in handy.\nSteps 👍 First, we need to obtain the file paths of the files we want to compare. Open your terminal and run the following command: code --diff /path/fileA /path/fileB, then press Enter. VSCode will open, displaying the differences between both files in a clear and user-friendly manner.\nAdditionally, any changes you make to the files during this comparison can be conveniently saved back to the original files.\nConclusion For swift and efficient file comparison, VSCode provides a valuable graphical user interface (GUI). This nifty trick can save time and help prevent mistakes, as you can instantly see how your changes impact the differences between the files.\n","date":"2023-11-07T16:58:58-06:00","permalink":"/p/comparing-files-in-vscode/","title":"Comparing Files in Vscode"},{"content":"Today, I\u0026rsquo;d like to present a small algorithmic problem and some solutions I have come up with.\nThe problem is as follows: Given a string, determine if the parentheses are balanced.\nBalanced parentheses mean that for every ( found, there must be a corresponding ) to balance it.\nInput and output It is guarantee that any given string will contain at least one parenthesis.\nin: \u0026#34;(hello\u0026#34; out: false in: \u0026#34;(hello()world)()\u0026#34; out: true in: \u0026#34;hello()(())\u0026#34; out: true in: \u0026#34;()\u0026#34; out: true in: \u0026#34;(\u0026#34; out: false Solutions Using a Hash Table (Dictionary) Let\u0026rsquo;s try a simple approach using a hash table.\nWe know that we need to have the same number of left and right parentheses at the end of a string traversal. If this doesn\u0026rsquo;t happen, then we can be sure that the string doesn\u0026rsquo;t have balanced parentheses.\nWe could use a hash table to count the occurrences of the left and right parentheses in the given string. Then we compare both results.\nWith this idea let\u0026rsquo;s implement a solution:\nfunc balancedParentheses(_ str: String) -\u0026gt; Bool { var dic = [String: Int]() str.forEach { char in if char == \u0026#34;(\u0026#34; || char == \u0026#34;)\u0026#34; { // What does default do? // If the key doesn\u0026#39;t exist add the key with an initial default value dic[String(char), default: 0] += 1 } } return dic[\u0026#34;(\u0026#34;] == dic[\u0026#34;)\u0026#34;] } This solution has a time complexity of $O(n)$.\nThe spatial complexity is $O(1)$, because we are always storing only two key-value pairs.\nDiscarding the hash table The previous solution works for the given problem but we are creating an extra data structure that can add unwanted complexity, maybe using a hash table for this specific problem is overkill.\nLet\u0026rsquo;s try a simpler approach.\nfunc balancedParentheses(_ str: String) -\u0026gt; Bool { var counter = 0 str.forEach { char in if char == \u0026#34;(\u0026#34; { counter += 1 } else if char == \u0026#34;)\u0026#34; { counter -= 1 } } return counter == 0 } Now we are using an Int to keep a count of the parentheses. We add 1 for a left one and subtract 1 for the right one. If the number of left and right parentheses is equal, then we can be sure that the parentheses are balanced and the final count should be 0.\nBy eliminating the hash table, we archive a code that is simpler to reason about by avoiding unnecessary layers.\nThe time and space complexity remains the same.\nUsing Stacks Another solution involves the use of Stacks, which can help us explore more advanced algorithmic techniques like backtracking.\nStacks are a common and useful data structure but are not implemented by default in Swift. They are typically implemented using an Array by restricting the insertion and deletion operations to the end of the structure.\nTo solve the given problem, we can use the Stack to store the left parentheses and popping only when a right parenthesis is found. As before, if the Stack finishes empty, then the parentheses in the string are balanced.\nLet\u0026rsquo;s start by implementing the Stack.\nstruct Stack\u0026lt;T\u0026gt; { private var storage: [T] = [] var isEmpty: Bool { storage.isEmpty } mutating func push(_ element: T) { storage.append(element) } @discardableResult mutating func pop() -\u0026gt; T? { storage.popLast() } } We mark pop with @discardableResult because we want to tell the program that we don\u0026rsquo;t care for the returned value and this can be discarded safely if we don\u0026rsquo;t assign it to any variable.\nNow let\u0026rsquo;s implement the solution.\nfunc balancedParentheses(_ str: String) -\u0026gt; Bool { var stack = Stack\u0026lt;Character\u0026gt;() for char in str { if char == \u0026#34;(\u0026#34; { stack.push(char) } else if char == \u0026#34;)\u0026#34; { if stack.isEmpty { return false } else { stack.pop() } } } return stack.isEmpty } We are using Character instead of String because the for in returns a single char when traversing the string.\nLet\u0026rsquo;s explain in more detail our solution.\nAs mentioned before we add to the Stack all ( found.\nIf we find a ) two possibilities can arise:\nThe Stack state is empty. The Stack state is not empty. In the first case we didn\u0026rsquo;t find a matching parenthesis so we can be certain that the parentheses in the string are not balanced.\nIn the second case, we can pop the previous right parenthesis and continue looking for the next parentheses.\nNow let\u0026rsquo;s focus on the last return, we are checking for the state of the Stack because the case of the Stack containing orphaned ( can occur, in that case the parentheses in the string are also not balanced.\nThe time complexity is $O(n)$ because we traverse the array only once.\nThe space complexity is $O(n)$ because in the worse case we would get a string containing only (, creating a stack with the same length as the string.\nConclusion In this small problem, we\u0026rsquo;ve observed that using stacks can lead to an increase in memory consumption without providing significant benefits. In the case of hash tables, the complexity is similar to the counting approach.\nFor problems that are straightforward and don\u0026rsquo;t involve complex matching, a simpler approach often yields better results.\nIt\u0026rsquo;s essential to understand the constraints of the problem to weigh the advantages and disadvantages of our solutions and ensure that we select the most suitable option for the task at hand.\nThis way of thinking can help us avoid overengineering solutions that add unnecessary complexity without providing real benefits.\n","date":"2023-10-25T21:26:45-06:00","permalink":"/p/balanced-parentheses-in-strings/","title":"Balanced Parentheses in Strings"},{"content":"The other day, I was working on a project that had a Git submodule. Initially, I found it a bit tricky to make it work, so I decided to create a short tutorial on this.\nWhat is a Git submodule Essentially, a Git submodule is a way to include one Git repository inside another Git repository. This creates a nested structure.\nSo, in simple words, a Git submodule is like a mini Git repository that you can include in your main Git repository to use and update external code or resources without actually putting all of their files directly in your project.\nThe crucial point to note is that, by default, the code of the submodule is not added to your Git repository when performing the initial git clone.\nHow to check the ulr of the git submodule To check the linked URLs of submodules we can use this command.\ngit config --file .gitmodules --get-regexp \u0026#39;submodule\\..*\\.url\u0026#39; How to clone a submodule There could be three situations:\nYou want to clone the git project with all its submodules for the first time. You\u0026rsquo;ve cloned the git project but didn\u0026rsquo;t fetch the submodules. You want to update the submodules in your project. Cloning a project with all submodules for the first time If you want to clone a project for the first time with all its submodules, you can run the following command.\nWith Git version 2.13 or newer.\ngit clone --recurse-submodules -j8 https://github.com/your/repo.git -j8 is an optional performance optimization that became available in version 2.8, and fetches up to 8 submodules at a time in parallel\nBefore Git 2.12.\ngit clone --recursive https://github.com/your/repo.git Cloning the submodules only If you\u0026rsquo;ve downloaded the project but forgot to fetch the submodules, you can use this command.\ngit submodule update --init --recursive If you\u0026rsquo;ve cloned only the main Git repository, you\u0026rsquo;ll be missing the submodule files, potentially causing your project to fail.\nUpdating the submodules If you want to update the code of the submodules, you need to run this command.\ngit submodule update --recursive --remote This will only update the branch registered in the .gitmodule, and by default, you will end up with a detached HEAD, unless \u0026ndash;rebase or \u0026ndash;merge is specified or the key submodule.$name.update is set to rebase, merge or none.\nAdding a submodule to a git repository To add another remote Git repository as a submodule to your current Git repository, you can use this command.\ngit submodule add https://github.com/your/repo.git path/to_save/in_repo The provided code allows you to specify the remote URL for the repository you want to use as a submodule, as well as the path within your project where you want to save the cloned code.\nDelete a Git submodule from your project This process is common but not that straightforward, here are the steps you need to do.\nThe files mentioned in the following steps are located in the root directory of your project, which is the default location for the Git files.\nDelete the relevant line from the .gitmodules file. Delete the relevant section from .git/config. Run git rm --cached path_to_submodule (no trailing slash). Commit and delete the now untracked submodule files. A trailing slash is the slash that can be added to the end of the path. submodules/my_submodule/ has a trailing slash while submodules/my_submodule doesn\u0026rsquo;t have one.\nConclusion Git submodules are a powerful feature for managing dependencies in your projects. They allow you to include external Git repositories within your main repository without cluttering it with their files. This can be especially handy when you\u0026rsquo;re working on projects that rely on external code or resources.\nWe\u0026rsquo;ve covered some essential aspects of working with Git submodules in this tutorial. You\u0026rsquo;ve learned what a Git submodule is and how to check the URLs of linked submodules. You also know how to clone a project with all its submodules, how to retrieve submodules if you\u0026rsquo;ve forgotten them, and how to keep them updated. Furthermore, we discussed adding and deleting a submodule from your Git repository.\nWith this knowledge, you\u0026rsquo;ll be better equipped to manage complex projects with Git submodules and streamline your workflow. So, don\u0026rsquo;t shy away from using them in your projects when they can help you manage external code more efficiently.\nHappy coding 🚀!\n","date":"2023-10-25T00:03:41-06:00","permalink":"/p/working-with-git-submodules/","title":"Working With Git Submodules"},{"content":"One of the most common problems we face when working with strings is modifying certain characters in the string.\nThis problem can be easily solved with the use of regular expressions (regex).\nBefore Swift 5.7 If you can\u0026rsquo;t uae the newest Swift you can achieve this by using the method replacingOccurrences(of:with:options).\nHere is an example of its use:\nlet s = \u0026#34;12abc34\u0026#34; let t = ss.replacingOccurrences( of: \u0026#34;[a-z]\u0026#34;, // regex with: \u0026#34;\u0026#34;, options: .regularExpression ) // output: \u0026#34;1234\u0026#34; If we want to do the opposite (i.e., to keep everything but lowercase letters), we can use the symbol ^ to invert the expression set.\nlet s = \u0026#34;12abc34\u0026#34; let t = ss.replacingOccurrences( of: \u0026#34;[^a-z]\u0026#34;, // regex with: \u0026#34;\u0026#34;, options: .regularExpression ) // output: \u0026#34;abc\u0026#34; After Swift 5.7 If we have access to a newer version of swift, you can use a simpler method to achieve the same result: replacing(_:with:maxReplacements:).\nlet s = \u0026#34;12abc34\u0026#34; let t = s.replacing(/[a-z]/, with: \u0026#34;\u0026#34;) // output: \u0026#34;1234\u0026#34; As before, we can use ^ to obtain the inverse result:\nlet s = \u0026#34;12abc34\u0026#34; let t = s.replacing(/[^a-z]/, with: \u0026#34;\u0026#34;) // output: \u0026#34;abc\u0026#34; Conclusion Regular expressions are quite powerful and can help us to solve problems with a simple predicate that describes what the want to obtain.\nRegex may seems hard to grasp at first, but we can start with little examples like these to gain experience with this useful tool.\n","date":"2023-10-09T19:15:49-06:00","permalink":"/p/replacing-elements-in-a-string/","title":"Replacing Elements in a String"},{"content":"Horizontal page carousels are a common element in iOS interfaces for presenting pages in a horizontal, swipeable format.\nThey are particularly useful for showcasing views with varying content.\nCode In SwiftUI, creating such elements is straightforward.\nLet\u0026rsquo;s define a SwiftUIView and use the following code:\nimport SwiftUI struct PageSliderView: View { // the data we want to present in each page, // this can be as simple as an array of string or as complex as an array of compound views. let pages = [\u0026#34;One\u0026#34;, \u0026#34;Two\u0026#34;, \u0026#34;Three\u0026#34;] var body: some View { TabView { ForEach(pages, id: \\.self) { Text(\u0026#34;Page \\($0)\u0026#34;) .font(.title) } } .tabViewStyle(PageTabViewStyle()) // set the tab view behaviour to page .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) // set the dot indicator to be visible } } #Preview { PageSliderView() } The code above showcases one of the advantages of declarative UI frameworks, where you describe what you want instead of instructing the computer how to do it.\nThis approach results in expressive and concise code.\nResult The outcome is impressive, especially when you consider the minimal code required.\nConclusion SwiftUI is a powerful tool that enables us to focus on describing the UI. With just a few lines of code, we can achieve results that would necessitate significantly more code in UIKit.\n","date":"2023-09-27T01:44:33-06:00","permalink":"/p/swiftui-horizontal-pages-carousel/","title":"SwiftUI – Horizontal Pages Carousel"},{"content":"To understand what copy-on-write means we need to delve into Swift\u0026rsquo;s type classification.\nObjects in Swift can be divided into two type groups: value types and reference types.\nThe main difference between the two is how they are managed in memory. Theoretically, Value types create a new copy on memory each time they are assigned to a new variable, conversely reference types share the same reference amongst all variables.\nWhat is copy-on-write? This is a technique used to manage the copy of objects efficiently.\nWhen we create a new assignment of a variable that holds a type supporting copy-on-write, the actual duplication of the data only occurs when there is a modification to the data through its references (data mutation).\nThis mechanism also occurs when we initialize or pass them as arguments.\nThis technique allow us to improve performance and reduce the use of costly copy operations.\nIn essence, we are postponing the allocation and copying of data in memory until we actually need it.\nHow does this affect Swift? We mentioned that, in Swift, value types are copied to a new memory region when we assign them to variables (copy semantics). This is partially true for built-in value-types and for custom types where we implement this functionality.\nThe whole process involves the extra step of copy-on-write mechanism that allows us to reduce unnecessary memory allocations, making the entire process more efficient and performant.\nCopy and move semantics is a complex topic and it\u0026rsquo;s out of the scope of this post, in simple terms here are some definitions of them:\nCopy semantics means that when you pass or assign an object, you create a new copy of that object’s data.\nMove semantics means that when you pass or assign an object, you transfer the ownership of that object’s data to another object.\nThis means that every time we make a new assignment, the copy allocation is delayed until we modify the actual data (mutation). This implies that at certain times, our value types can refer to the same memory region, behaving as reference types.\nDoes all value types uses copy-on-write in Swift? We have this behaviour for free in Collection types: Arrays, Sets, Dictionaries, and Strings.\nFor custom value types, we need to manually implement it using the function isKnownUniquelyReferenced(_:). Apple provides guidance on how to apply this mechanism here.\nCode example To demonstrate this functionality let\u0026rsquo;s use an Array collection:\n/// Prints the address of the given array /// - Parameter bytes: the array pointer func printAddress(_ bytes: UnsafeRawBufferPointer) { print(NSString(format: \u0026#34;%p\u0026#34;, Int(bitPattern: bytes.baseAddress))) } var nums1 = [1,2,3] var nums2 = nums1 // copying nums1.withUnsafeBytes(printAddress) // 0x60000170c0e0 nums2.withUnsafeBytes(printAddress) // 0x60000170c0e0 nums2.append(4) nums1.withUnsafeBytes(printAddress) // 0x60000170c0e0 nums2.withUnsafeBytes(printAddress) // 0x60000210c6b0 (copy-on-write happens) In the above code we can observe how the copy-on-write delays the duplication on a different memory location until we made some changes to the underlying data.\nWarning: There was a change in the Swift 5.3 compiler that made the withUnsafeBytes (_:) method of String internal, meaning that it can only be accessed within the same module as the String type. Meaning this way of printing addresses won\u0026rsquo;t work for Strings.\nAnother example: Passing collections to functions Let\u0026rsquo;s explore passing collections as arguments in functions.\nBy default, when we pass a type to a function this is immutable by default (it cannot be changed). This improves the predictability and safety of our code.\nAnother consequence of this immutability in function\u0026rsquo;s parameters is the use of copy-on-write. Since we cannot mutate our value types, no memory copy occurs and we get a reference to the same value. This helps to avoid unnecessary cpu and memory work.\nLet\u0026rsquo;s demonstrate this fact with the following code.\n/// Prints the address of the given array /// - Parameter bytes: the array pointer func printAddress(_ bytes: UnsafeRawBufferPointer) { print(NSString(format: \u0026#34;%p\u0026#34;, Int(bitPattern: bytes.baseAddress))) } /// A functions that receives an array as argument and prints its memory address and content. /// - Parameter array: the array being passed. func foo(_ array: [Int]) { array.withUnsafeBytes(printAddress) print(array) } var nums = [1, 2, 3] nums.withUnsafeBytes(printAddress) // 0x6000017001a0 foo(nums) // 0x6000017001a0 (same memory address) The previous code shows that the data we get inside the function is the same as the one being passed. This indicates the use of copy-on-write.\nThis is great from a performance point of view because we don\u0026rsquo;t need to optimize the memory copy for built-in types.\nIn the case of custom value types, the copy does occur if we don\u0026rsquo;t add the copy-on-write functionality manually.\nAdvantages and disadvantages The use of copy-on-write can be a great way to reduce memory usage by having several references to the same object in memory, this also helps with performance when we use it in data that rarely change and is shared across several parts.\nConversely, it can be hard to implement for our custom types, specially if our types are complex. This can limit the applicability of this technique by adding complexity to our code.\nConclusion Copy-on-write is a great way to improve memory management and boost performance of our value types. Despite some complexities involved in its manual implementation for custom types, it\u0026rsquo;s a great tool that we need to understand to be able to write performant code when implementing low level abstractions.\n","date":"2023-09-25T01:30:20-06:00","permalink":"/p/swift-copy-on-write/","title":"Swift copy-on-write"},{"content":"The other day, I needed to set up a PostgreSQL database, and after considering various solutions, I decided to use Docker. In this tutorial, I\u0026rsquo;ll share the solution that worked for my problem.\nCreating a PostgreSQL Database Using Docker Compose Docker Compose is a powerful tool that allows us to manage multiple containers from a single central point. You might think it\u0026rsquo;s a bit much for setting up a database, but I found it surprisingly easy to set up and use.\nLet\u0026rsquo;s start!\nCreating the docker-compose.yml File Docker Compose requires a configuration file named docker-compose.yml where we define and configure our containers. Writing this file is straightforward; here\u0026rsquo;s the one I used for my database:\nversion: \u0026#39;3\u0026#39; services: postgres-db: image: postgres:latest container_name: postgres_container volumes: - \u0026#34;/path_to_local_storage:/var/lib/postgresql/data\u0026#34; environment: - POSTGRES_DB=my_db - POSTGRES_USER=user - POSTGRES_PASSWORD=password ports: - \u0026#39;5433:5432\u0026#39; In the above file:\nWe define a service named postgres-db containing the PostgreSQL database image. You can customize the container_name to your preference. To ensure data persistence and choose a custom storage location, define your preferred local path in the \u0026lsquo;volumes\u0026rsquo; section. Set your database credentials (user, password) and the default database name in the environment section. The port mapping is configured to use the default PostgreSQL ports. You can adjust this mapping if necessary; the format is local_machine_port:docker port. Starting the database To start your database, open a terminal in the directory where your docker-compose.yml is located (or specify a relative path to the file) and run this command (assuming you\u0026rsquo;re using Docker Compose 2):\ndocker compose -p example up -d This command creates the containers defined in the compose file. You can customize the project name with the -p property.\nStopping the database To stop your database, use this command:\ndocker compose down Connecting locally to the database Connecting to the database locally on your computer is simple. Access http://localhost:5433 using your chosen username and password. While I use DataGrip to connect, any database software can connect to it.\nTroubleshooting During the container creation process, I encountered some common issues, along with their solutions.\nIssue: The image exited with code 1. If your container exits immediately when launched, check the container logs by running:\ndocker logs my_container_name I discovered that my data directory contained hidden files that caused this issue. Deleting these hidden files allowed the database to start and function as expected.\nIssue: The mounting point is not valid. If you encounter issues starting the image, it may be related to the local path name. If your path contains spaces, a simple workaround is to enclose the path in double quotes.\nConclusion By utilizing docker-compose, we can easily start and manage data persistence for our databases. This method provides a straightforward and quick way to create databases with data persistence. I hope this tutorial proves as helpful to you as it was to me.\nHappy coding!\n","date":"2023-09-18T19:47:33-06:00","permalink":"/p/how-to-create-databases-using-docker/","title":"How to Create Databases Using Docker"},{"content":"Let\u0026rsquo;s continue working with more LeetCode problems, this time focusing on an easy one.\nWe need to find if, in a given array, we can find two numbers such that they can be added up to a certain target value. We can be certain that there will be only one solution.\nSolution Brute force As with other problems, one first approach would be to compare all possible combinations to find the two numbers that adds to the target. This solution is easy to implement but it has a bad time performance of $O(n^2)$.\nfunc twoSum(_ nums: [Int], _ target: Int) -\u0026gt; [Int] { var indices = [Int]() for i in nums.indices { for j in (i + 1)..\u0026lt;nums.count { if nums[i] + nums[j] == target { indices.append(contentsOf: [i, j]) } } } return indices } You may be wondering why we have (i + 1) in the inner loop. This is to avoid comparing the same value with itself, if we do that we may get a wrong solutions to certain inputs (one example: [3, 2, 4] and 6).\nThis solutions solves the problem, but it\u0026rsquo;s slow with a time complexity of $O(n^2)$. Let\u0026rsquo;s try to find another approach to improve this.\nUsing a Hash Table Let\u0026rsquo;s try to think of the problem as a math equation, we want two values that add up to a certain third value.\nThis can be represented as the following simple equation.\n$$ a + b = target $$How can this help us? Notice that we already have two of three values of the equation for any given iteration. So we are searching for the third value, this is the value $b$ that can solve the previous equation.\n$$ b = target - a $$This missing value $b$ is just the difference between the target and the current value.\nBy knowing this, we can store somehow the values and indices of the array and check if some $b$ that can solves the equation is presented in our storing objet. When that happens we are done, and we can return the indices for the current value and the one stored for $b$.\nWe need an object able to save a value with its index, this is a perfect use for a Hash table. In Swift we have one already build-in, it\u0026rsquo;s called a Dictionary.\nThis data structure saves a key-value pair, like a real dictionary do. It\u0026rsquo;s also important to know that insertion and retrieval takes $O(1)$ time complexity, which make it ideal for this problem.\nHere is the solution using this approach.\nfunc twoSum(_ nums: [Int], _ target: Int) -\u0026gt; [Int] { // Storing key: number, value: index in array var dic = [Int:Int]() for (currentNumberIndex, value) in nums.enumerated() { let diff = target - value if let missingNumberIndex = dic[diff] { return [missingNumberIndex, currentNumberIndex] } dic[value] = currentNumberIndex } return [-1,-1] } This solution is easy to read and quite efficient because we only check each element once. The time complexity is $O(n)$ and since we are storing each element in a dictionary, the space complexity is also $O(n)$.\nHere we are using some cool Swift features:\n.enumerated(): returns a tuple containing the current index and value. Checking for optionals in dictionaries: we are using a if let statement to check whether the value we are missing is already on the dictionary. Conclusion This problem provides valuable insights into the efficient use of dictionaries in Swift. We started with a brute force approach that had a time complexity of $O(n^2)$, but then we optimized our solution using a hash table (Dictionary) to achieve a linear time complexity of $O(n)$. By employing the concept of key-value pairs and leveraging Swift\u0026rsquo;s features like .enumerated(), we were able to find a more elegant and efficient solution.\nExploring different approaches and understanding when to use data structures like dictionaries can greatly enhance our problem-solving skills as developers. Happy coding!\n","date":"2023-09-14T14:32:44-06:00","permalink":"/p/leetcode-two-sum/","title":"LeetCode – Two Sum"},{"content":"Hello everyone, in this post I want to explore a solution to the Contains Duplicates LeetCode problem using Swift. Most solutions use Python and I think it would be a good idea to try to use Swift to explore how well it behaves for the LeetCode style questions.\nThe Problem This problem states that we have an array of integers and it may contain some values that are duplicated, so we need to write a function that check if the given array contains any element more than once.\nHere are some examples of valid inputs and outputs:\n# Example 1 Input: nums = [1,2,3,1] Output: true # Example 2 Input: nums = [1,2,3,4] Output: false Solution To solve this problem we need to find a way to count for at least one duplicity in the array, if this condition happens we can be sure that there are duplicates.\nThere are several approaches to solve this.\nBrute force We could think that if we select each element and compare with each other we would end up finding the duplicate, this solution involves the use of two nested for.\nAs we can observe in the image, when we compare each element with every other element, we create a scalability problem as our data set grows.\nAs an example, in a worse case nested comparison, if we have $30$ elements, we would perform $900$ operations; for $900$ elements, it would be $810,000$. This is why $O(n^2)$ time solutions are problematic when dealing with large data sets. To achieve scalability, we need to optimize our solutions for faster algorithms.\nfunc containsDuplicateBruteForce(_ nums: [Int]) -\u0026gt; Bool { for n in 0..\u0026lt;nums.count { for m in (n + 1)..\u0026lt;nums.count { if nums[n] == nums[m] { return true } } } return false } Our solution works but, as we mentioned before, it is not ideal due to its time complexity, resulting in $O(n^2)$. The problem here is that we are doing inefficient comparisons, we can do better.\nSets We are looking for duplicates, so one possible approach for this problem is to store somehow the elements in a new container that doesn\u0026rsquo;t allow duplicates, then we can add them and if some element was not able to be inserted we can be sure that there are duplicates.\nWhat data structure have the property of not allowing duplicates? It\u0026rsquo;s the Set!\nLucky us, Swift already comes with a build-in Set data structure.\nSets are data structures that don\u0026rsquo;t allow duplicates. We can iterate over the array and insert each element into a new Set. If the insertion fails (i.e., the element already exists in the Set), we return true. Sounds easy enough, let\u0026rsquo;s code it!.\nfunc containsDuplicate(_ nums: [Int]) -\u0026gt; Bool { // Define the Set of integers var set = Set\u0026lt;Int\u0026gt;() // Iterate over the array to generate the Set, if the set returns false we found a duplicate for num in nums where !set.insert(num).inserted { return true } // if all elements were inserted then there is no duplicated value return false } According to some pages, inserting into a Set in Swift uses internally a hash map, this mean that the insertion takes in average $O(1)$, making this solution $O(n)$.\nThis solution is already quite efficient because we are only iterating over all elements only once, but can we do a simpler code that express the same idea in a more concise way?\nWe are iterating over the elements to create a new container with its own copy of values, at the end we will end up with two objects. We can take advantage of this in the following manner: if the number of elements on both containers is not the same, then we can assume there are duplicates, in case that both contains the same number of elements we can be sure that there are no duplicates. This solution may lead us to a more expressive and concise code, so let explore it.\nfunc containsDuplicate(_ nums: [Int]) -\u0026gt; Bool { // Define the Set of integers var set = Set\u0026lt;Int\u0026gt;() // Iterate over the array to generate the Set, if the set returns false we found a duplicate for num in nums { set.insert(num) } // if both containers have the same number of elements, they are equal and no duplicates were found, conversely there are duplicates return set.count \u0026lt; nums.count } This is looking simpler to read, but there is one last thing. Set constructors can accept Arrays to instantiate them, we could use this handy property to make a one line solution!.\nfunc containsDuplicate(_ nums: [Int]) -\u0026gt; Bool { // If the count of unique elements in the Set is less than the total count, there are duplicates Set(nums).count \u0026lt; nums.count } This simple line of code solves the problem in an elegant and concise way, we are creating a new set and then calling .count on it, then we use that value to compare it to the size of the original array and returning the result.\nThe final time complexity of this solution is $O(n)$, because we are implicitly iterating over the array when we create the Set.\nConclusion I hope this post helps you understand how Swift features can be used to create elegant and efficient solutions. Thanks for reading, and until next time!\n","date":"2023-09-10T07:40:04-06:00","permalink":"/p/leetcode-contains-duplicates/","title":"LeetCode – Contains Duplicates"},{"content":"Hey there, tech enthusiasts! 📱, this is my first time blogging 🥳.\nI decided to create this blog to register all of my learning in the world of programming. I\u0026rsquo;m so exited to share what I\u0026rsquo;m learning in this cool journey.\nI\u0026rsquo;m bad for making intros, so if something new comes to my mind I\u0026rsquo;ll update this post. 😅\nWhile my main focus right now is iOS development, that doesn\u0026rsquo;t mean I\u0026rsquo;m not intrigued by the myriad of options technology has to offer. I\u0026rsquo;m open to exploring and writing about various tech topics.\nCheers to learning and growing together in the world of code! 🥳💻\u0026quot;\n","date":"2023-09-10T06:58:41-06:00","permalink":"/p/hello-world/","title":"Hello World"}]