<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[iOS development by tanaschita.com]]></title><description><![CDATA[Learn how to develop iOS and Swift applications for Apple's platforms with quick guides tutorials, from beginner to advanced.]]></description><link>https://tanaschita.com</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 08 Oct 2026 07:58:51 GMT</lastBuildDate><item><title><![CDATA[Supporting Differentiate Without Color in SwiftUI]]></title><description><![CDATA[Learn how to communicate status with symbols and shapes instead of relying on color alone.]]></description><link>https://tanaschita.com/ios-accessibility-differentiate-without-color</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-differentiate-without-color</guid><pubDate>Sun, 11 Oct 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Color can make the state of an interface easy to recognize at a glance. But if color is the only difference between two states, that information may not be available to everyone.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll use SwiftUI&amp;#x27;s &lt;code&gt;accessibilityDifferentiateWithoutColor&lt;/code&gt; environment value to add a second visual cue when the corresponding system setting is enabled.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Starting with a color-only status&lt;/h3&gt;&lt;p&gt;Suppose we use a colored circle to show whether something is available:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Circle()
    .fill(isAvailable ? .green : .red)
    .frame(width: 28, height: 28)
    .accessibilityLabel(
        isAvailable ? &amp;quot;Available&amp;quot; : &amp;quot;Unavailable&amp;quot;
    )
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The accessibility label communicates the state to assistive technologies such as VoiceOver, but the visible interface still relies entirely on the difference between green and red.&lt;/p&gt;&lt;p&gt;We can make the two states visually distinct by adding a checkmark or an xmark when the user asks the system to differentiate without color.&lt;/p&gt;&lt;h3&gt;Reading the system preference&lt;/h3&gt;&lt;p&gt;Users can enable &lt;em&gt;Differentiate Without Color&lt;/em&gt; in the Settings app under &lt;code&gt;Accessibility → Display &amp;amp; Text Size → Differentiate Without Color&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;SwiftUI exposes that preference as a Boolean environment value:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(\.accessibilityDifferentiateWithoutColor)
private var differentiateWithoutColor
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because it is an environment value, SwiftUI updates views that read it when the setting changes.&lt;/p&gt;&lt;h3&gt;Adding a second visual cue&lt;/h3&gt;&lt;p&gt;We can now overlay a symbol when the preference is enabled:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct AvailabilityIndicator: View {
    @Environment(\.accessibilityDifferentiateWithoutColor)
    private var differentiateWithoutColor

    let isAvailable: Bool

    var body: some View {
        Circle()
            .fill(isAvailable ? .green : .red)
            .frame(width: 28, height: 28)
            .overlay {
                if differentiateWithoutColor {
                    Image(systemName: isAvailable ? &amp;quot;checkmark&amp;quot; : &amp;quot;xmark&amp;quot;)
                        .font(.caption.bold())
                        .foregroundStyle(.white)
                }
            }
            .accessibilityLabel(
                isAvailable ? &amp;quot;Available&amp;quot; : &amp;quot;Unavailable&amp;quot;
            )
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the setting disabled, the indicator keeps its original appearance. With the setting enabled, its meaning no longer depends on distinguishing the two colors.&lt;/p&gt;&lt;p&gt;Depending on the interface, the additional cue can also be a text, different shapes, patterns or a change in position may communicate the distinction more clearly.&lt;/p&gt;&lt;h3&gt;Designing beyond the preference&lt;/h3&gt;&lt;p&gt;Depending on the design, it may also make sense to show the checkmark and xmark by default. This makes the states distinguishable without color for everyone and removes the need for a conditional adaptation.&lt;/p&gt;&lt;p&gt;&lt;code&gt;accessibilityDifferentiateWithoutColor&lt;/code&gt; is useful when the additional visual cue works better as an alternative presentation, for example when symbols would otherwise add too much visual density to a compact interface.&lt;/p&gt;&lt;h3&gt;Testing both appearances&lt;/h3&gt;&lt;p&gt;We can use previews to compare the indicator with the preference disabled and enabled:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;#Preview(&amp;quot;Default appearance&amp;quot;) {
    HStack(spacing: 20) {
        AvailabilityIndicator(isAvailable: true)
        AvailabilityIndicator(isAvailable: false)
    }
    .padding()
}

#Preview(&amp;quot;Differentiate without color&amp;quot;) {
    HStack(spacing: 20) {
        AvailabilityIndicator(isAvailable: true)
        AvailabilityIndicator(isAvailable: false)
    }
    .padding()
    .environment(\.accessibilityDifferentiateWithoutColor, true)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When testing the settings on a device or simulator, Xcode&amp;#x27;s Accessibility Inspector can override &lt;em&gt;Differentiate Without Color&lt;/em&gt; as well, which makes it useful for checking several screens without repeatedly changing the system setting.&lt;/p&gt;&lt;p&gt;A grayscale color filter can reveal places that rely heavily on color, but it isn&amp;#x27;t a simulation of how every person with a color vision difference sees the interface. It is a useful additional check, not a replacement for providing another visual cue.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;accessibilityDifferentiateWithoutColor&lt;/code&gt; environment value tells us whether the user enabled the corresponding system setting. We can use it to provide an alternative presentation with symbols, shapes, text or other visual cues.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Adapting SwiftUI layouts for large Dynamic Type sizes]]></title><description><![CDATA[Learn how to restructure SwiftUI layouts when larger accessibility text sizes leave too little room for content.]]></description><link>https://tanaschita.com/ios-accessibility-dynamic-type-layouts</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-dynamic-type-layouts</guid><pubDate>Sun, 04 Oct 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A layout that works well at the default text size can become cramped when Dynamic Type increases the content size. Supporting Dynamic Type therefore also means allowing the layout to grow or change its structure.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll adapt a task row that works horizontally at standard text sizes but becomes difficult to read at larger accessibility sizes. For an introduction to scaling system and custom fonts, check out the article on &lt;a href=&quot;/ios-accessibility-dynamic-type/&quot;&gt;supporting Dynamic Type in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Starting with a horizontal layout&lt;/h3&gt;&lt;p&gt;Suppose a task row displays its title next to a due date:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;HStack(alignment: .firstTextBaseline, spacing: 12) {
    Text(title)
        .font(.headline)
        .frame(maxWidth: .infinity, alignment: .leading)

    Label(dueDate, systemImage: &amp;quot;calendar&amp;quot;)
        .font(.subheadline)
        .foregroundStyle(.secondary)
}
.padding()
.background(.quaternary, in: .rect(cornerRadius: 12))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At a standard text size, both pieces of information have enough horizontal space. At an accessibility size, the title and due date grow while still sharing the same row. The title may wrap into a narrow column even though it would remain easy to read if it could use the full width.&lt;/p&gt;&lt;p&gt;To solve this, we can place the due date below the title when an accessibility size is active.&lt;/p&gt;&lt;h3&gt;Switching layout for accessibility sizes&lt;/h3&gt;&lt;p&gt;SwiftUI exposes the current text size through the &lt;code&gt;dynamicTypeSize&lt;/code&gt; environment value. Its &lt;code&gt;isAccessibilitySize&lt;/code&gt; property tells us whether the user selected one of the larger accessibility categories.&lt;/p&gt;&lt;p&gt;We can use that value to choose between &lt;code&gt;HStackLayout&lt;/code&gt; and &lt;code&gt;VStackLayout&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private struct TaskRow: View {
    @Environment(\.dynamicTypeSize) private var dynamicTypeSize

    private var taskLayout: AnyLayout {
        if dynamicTypeSize.isAccessibilitySize {
            AnyLayout(
                VStackLayout(alignment: .leading, spacing: 8)
            )
        } else {
            AnyLayout(
                HStackLayout(alignment: .firstTextBaseline, spacing: 12)
            )
        }
    }

    var body: some View {
        taskLayout {
            Text(title)
                .font(.headline)
                .frame(maxWidth: .infinity, alignment: .leading)

            Label(dueDate, systemImage: &amp;quot;calendar&amp;quot;)
                .font(.subheadline)
                .foregroundStyle(.secondary)
        }
        .padding()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;AnyLayout&lt;/code&gt; lets both layouts contain the same views. At standard sizes, the title and date remain next to each other. At accessibility sizes, the vertical layout gives each of them the full row width.&lt;/p&gt;&lt;h3&gt;How AnyLayout works&lt;/h3&gt;&lt;p&gt;Although &lt;code&gt;AnyLayout&lt;/code&gt; is a type-erased layout, it doesn&amp;#x27;t erase the child views in the way that &lt;code&gt;AnyView&lt;/code&gt; erases a view. It only hides whether the active layout algorithm comes from &lt;code&gt;HStackLayout&lt;/code&gt;, &lt;code&gt;VStackLayout&lt;/code&gt; or another type conforming to the &lt;code&gt;Layout&lt;/code&gt; protocol.&lt;/p&gt;&lt;p&gt;The trailing closure still builds the &lt;code&gt;Text&lt;/code&gt; and &lt;code&gt;Label&lt;/code&gt; as the same child views. A type conforming to &lt;code&gt;Layout&lt;/code&gt; receives a &lt;code&gt;LayoutSubviews&lt;/code&gt; collection containing a proxy for each child, which it can measure and position. &lt;code&gt;AnyLayout&lt;/code&gt; forwards those proxies to the wrapped layout algorithm. When the selected layout changes, SwiftUI therefore rearranges the existing children instead of replacing their view hierarchy, preserving their identity and state.&lt;/p&gt;&lt;h3&gt;Choosing when to change the layout&lt;/h3&gt;&lt;p&gt;A narrow device, a long localized title or additional row content might require the vertical layout at an earlier text size.&lt;/p&gt;&lt;p&gt;If the decision is primarily about the available width rather than the selected text size, &lt;code&gt;ViewThatFits&lt;/code&gt; may be a better choice because it selects a layout based on which version fits. The article on &lt;a href=&quot;/swiftui-viewthatfits-container/&quot;&gt;building adaptive layouts with ViewThatFits&lt;/a&gt; covers that approach in more detail.&lt;/p&gt;&lt;h3&gt;Testing at larger text sizes&lt;/h3&gt;&lt;p&gt;We can preview the same row at different Dynamic Type sizes by changing the environment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;#Preview(&amp;quot;Default text size&amp;quot;) {
    TaskRow(
        title: &amp;quot;Prepare the accessibility audit article&amp;quot;,
        dueDate: &amp;quot;Today&amp;quot;
    )
    .padding()
}

#Preview(&amp;quot;Accessibility 3&amp;quot;) {
    TaskRow(
        title: &amp;quot;Prepare the accessibility audit article&amp;quot;,
        dueDate: &amp;quot;Today&amp;quot;
    )
    .padding()
    .environment(\.dynamicTypeSize, .accessibility3)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Supporting Dynamic Type doesn&amp;#x27;t end when text grows with the user&amp;#x27;s preferred size. The surrounding layout also needs to give that text enough room.&lt;/p&gt;&lt;p&gt;&lt;code&gt;isAccessibilitySize&lt;/code&gt; and &lt;code&gt;AnyLayout&lt;/code&gt; keep that solution compact: one environment value drives the decision, and the same child views are reused in both layouts without losing their identity or state.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Automating accessibility audits for SwiftUI apps with XCTest]]></title><description><![CDATA[Learn how to detect common accessibility issues automatically as part of a UI test suite.]]></description><link>https://tanaschita.com/ios-accessibility-automated-audits</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-automated-audits</guid><pubDate>Sun, 27 Sep 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many standard SwiftUI views include accessibility support out of the box. However, custom layouts, styling and content can still lead to missing descriptions, insufficient contrast, clipped text or controls that are difficult to activate.&lt;/p&gt;&lt;p&gt;XCTest accessibility audits allow us to detect many of these issues automatically in a UI test. In this article, we&amp;#x27;ll look at how to do that. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding an accessibility audit to a UI test&lt;/h3&gt;&lt;p&gt;We can add accessibility audits to an Xcode UI test target using XCTest. The audit API is available on &lt;code&gt;XCUIApplication&lt;/code&gt;, so it isn&amp;#x27;t available from a unit test or a Swift Testing test function.&lt;/p&gt;&lt;p&gt;To navigate to a specific screen reliably, the UI test needs stable identifiers. We assign them to the corresponding SwiftUI views using &lt;code&gt;accessibilityIdentifier(_:)&lt;/code&gt;, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;Library&amp;quot;)
    .accessibilityIdentifier(&amp;quot;screen.library.title&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With identifiers in place, a first audit example may look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import XCTest

final class LibraryAccessibilityTests: XCTestCase {
    private var app: XCUIApplication!

    @MainActor
    override func setUpWithError() throws {
        app = XCUIApplication()
        app.launch()
    }

    @MainActor
    func testOverview() throws {
        app.tabBars.buttons[&amp;quot;tab.library&amp;quot;].tap()
        XCTAssertTrue(
            app.staticTexts[&amp;quot;screen.library.title&amp;quot;]
                .waitForExistence(timeout: 2)
        )

        try app.performAccessibilityAudit()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;performAccessibilityAudit()&lt;/code&gt; inspects the currently visible screen. If it finds an issue, the UI test fails and Xcode reports the affected element and audit type. We don&amp;#x27;t need to add a separate assertion.&lt;/p&gt;&lt;p&gt;By default, the method runs all applicable audit types. On iOS, these include checks for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;missing or insufficient element descriptions&lt;/li&gt;&lt;li&gt;small hit regions&lt;/li&gt;&lt;li&gt;insufficient contrast&lt;/li&gt;&lt;li&gt;inaccessible content that may need to be exposed as separate elements&lt;/li&gt;&lt;li&gt;clipped text&lt;/li&gt;&lt;li&gt;missing Dynamic Type support&lt;/li&gt;&lt;li&gt;invalid accessibility traits&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The audit inspects the state currently presented by the app.&lt;/p&gt;&lt;h3&gt;Auditing relevant screens and states&lt;/h3&gt;&lt;p&gt;To cover different screens and states, we can use familiar UI testing techniques to navigate to each one before running the audit. For example, another test can open a book from the library and audit its detail screen:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@MainActor
func testBookDetails() throws {
    app.tabBars.buttons[&amp;quot;tab.library&amp;quot;].tap()

    let book = app.buttons[&amp;quot;library.book.swift-concurrency&amp;quot;]
    XCTAssertTrue(book.waitForExistence(timeout: 2))
    book.tap()

    XCTAssertTrue(
        app.staticTexts[&amp;quot;screen.bookDetails.title&amp;quot;]
            .waitForExistence(timeout: 2)
    )

    try app.performAccessibilityAudit()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Waiting for the detail title before starting the audit prevents the test from accidentally inspecting the library while the transition is still in progress.&lt;/p&gt;&lt;p&gt;Useful cases are states that expose different content or interactions, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;an empty list and a populated list&lt;/li&gt;&lt;li&gt;a form before and after validation errors appear&lt;/li&gt;&lt;li&gt;loading, success and error states&lt;/li&gt;&lt;li&gt;sheets, alerts and menus&lt;/li&gt;&lt;li&gt;screens at important points in the main user workflows&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Handling known audit issues&lt;/h3&gt;&lt;p&gt;Sometimes an audit reports an issue that we have reviewed and knowingly accepted, for example while a design change is still pending. An audit can also occasionally report a false positive.&lt;/p&gt;&lt;p&gt;In those cases, we can use the optional issue handler to make an exception for that exact finding:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@MainActor
func testFeaturedBooks() throws {
    app.tabBars.buttons[&amp;quot;tab.library&amp;quot;].tap()
    XCTAssertTrue(
        app.staticTexts[&amp;quot;screen.library.featured.title&amp;quot;]
            .waitForExistence(timeout: 2)
    )

    try app.performAccessibilityAudit { issue in
        issue.auditType == .contrast &amp;amp;&amp;amp;
        issue.element?.identifier == &amp;quot;screen.library.featured.title&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The handler returns &lt;code&gt;true&lt;/code&gt; only for the accepted contrast issue on the featured title. Every other issue continues to fail the test.&lt;/p&gt;&lt;h3&gt;Investigating issues with Accessibility Inspector&lt;/h3&gt;&lt;p&gt;When an automated audit fails, Accessibility Inspector helps us inspect the issue interactively. We can open it from &lt;code&gt;Xcode → Open Developer Tool → Accessibility Inspector&lt;/code&gt;, select the running app and switch to the Audits pane.&lt;/p&gt;&lt;p&gt;After running an audit for the current screen, we can:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;select an issue to highlight the affected element in the app&lt;/li&gt;&lt;li&gt;inspect its accessibility label, value, traits and available actions&lt;/li&gt;&lt;li&gt;read the explanation and suggested fix&lt;/li&gt;&lt;li&gt;save the audit results as an HTML report&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Accessibility Inspector uses the same kinds of checks as &lt;code&gt;performAccessibilityAudit()&lt;/code&gt;, but it is better suited to understanding and debugging a reported issue. Automated UI tests are better suited to detecting when that issue returns later.&lt;/p&gt;&lt;h3&gt;Understanding the limits of automated audits&lt;/h3&gt;&lt;p&gt;Passing an audit doesn&amp;#x27;t guarantee that an app provides a good accessibility experience. An audit can detect a missing label, but it can&amp;#x27;t reliably decide whether the label is useful in context or whether the VoiceOver navigation order makes sense for the workflow.&lt;/p&gt;&lt;p&gt;Automated audits should therefore be complemented by testing the main workflows with assistive technologies such as VoiceOver.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;XCTest accessibility audits give us a repeatable way to catch common accessibility regressions during development. By running them after navigating to meaningful screens and states, we can include accessibility checks in the regular UI test suite rather than relying on a one-time review before release.&lt;/p&gt;&lt;p&gt;When an audit fails, Accessibility Inspector helps us understand the affected element and resolve the issue. Together, automated audits and focused investigation provide fast feedback whenever a UI change introduces a common accessibility problem.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>testing</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Parameterized tests and test organization with Swift Testing]]></title><description><![CDATA[Learn how to cover multiple inputs with parameterized tests and organize a growing Swift Testing test suite.]]></description><link>https://tanaschita.com/testing-swift-testing-parameterized-tests</link><guid isPermaLink="false">https://tanaschita.com/testing-swift-testing-parameterized-tests</guid><pubDate>Sun, 20 Sep 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a test suite grows, we often end up writing multiple test functions that exercise the same behavior with slightly different inputs. Each test is simple on its own, but the repetition makes the whole suite harder to scan and maintain.&lt;/p&gt;&lt;p&gt;Swift Testing addresses this with parameterized tests. They let us pass a collection of arguments to one test function and create a separate test case for each argument. We can then use suites and tags to keep those cases organized as the project grows.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Replace repetitive tests with a parameterized test.&lt;/li&gt;&lt;li&gt;Model more complex inputs as test cases.&lt;/li&gt;&lt;li&gt;Test combinations of multiple argument collections.&lt;/li&gt;&lt;li&gt;Organize tests with suites, nested suites and tags.&lt;/li&gt;&lt;li&gt;Keep parallel execution in mind when structuring a suite.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you&amp;#x27;re new to the framework, you can start with this &lt;a href=&quot;/testing-swift-testing-overview/&quot;&gt;developer guide on Swift Testing&lt;/a&gt;, which covers test functions, expectations and the differences from XCTest.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Replacing repetitive tests with a parameterized test&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s say our app contains a formatter that turns a title into a URL slug:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SlugFormatter {

    let separator: String

    init(separator: String = &amp;quot;-&amp;quot;) {
        self.separator = separator
    }

    func format(_ title: String) -&amp;gt; String {
        title
            .lowercased()
            .replacingOccurrences(of: &amp;quot; &amp;quot;, with: separator)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We could test several inputs by creating a separate test function for each one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Testing
@testable import ExampleApp

struct SlugFormatterTests {

    @Test func formatsSingleWord() {
        let result = SlugFormatter().format(&amp;quot;Swift&amp;quot;)
        #expect(result == &amp;quot;swift&amp;quot;)
    }

    @Test func formatsMultipleWords() {
        let result = SlugFormatter().format(&amp;quot;Swift Testing&amp;quot;)
        #expect(result == &amp;quot;swift-testing&amp;quot;)
    }

    @Test func formatsEmptyString() {
        let result = SlugFormatter().format(&amp;quot;&amp;quot;)
        #expect(result == &amp;quot;&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The arrange and assertion logic is identical in all three tests. Only the input and expected output change. We can keep each pair together in a small test-case type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SlugTestCase: Sendable {
    let title: String
    let expectedSlug: String
}

extension SlugTestCase {
    static let examples = [
        SlugTestCase(title: &amp;quot;Swift&amp;quot;, expectedSlug: &amp;quot;swift&amp;quot;),
        SlugTestCase(title: &amp;quot;Swift Testing&amp;quot;, expectedSlug: &amp;quot;swift-testing&amp;quot;),
        SlugTestCase(title: &amp;quot;&amp;quot;, expectedSlug: &amp;quot;&amp;quot;)
    ]
}

@Test(arguments: SlugTestCase.examples)
func formatsTitle(_ testCase: SlugTestCase) {
    let result = SlugFormatter().format(testCase.title)
    #expect(result == testCase.expectedSlug)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Swift Testing creates an individual test case for every element in the collection and passes that element to the test function. If one case fails, Xcode reports the corresponding argument, which makes the failing input easier to identify than a failure inside a manual loop.&lt;/p&gt;&lt;p&gt;As a shorter alternative, we can keep the inputs and expected outputs in separate collections and combine them with &lt;code&gt;zip&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Test(
    arguments: zip(
        [&amp;quot;Swift&amp;quot;, &amp;quot;Swift Testing&amp;quot;, &amp;quot;&amp;quot;],
        [&amp;quot;swift&amp;quot;, &amp;quot;swift-testing&amp;quot;, &amp;quot;&amp;quot;]
    )
)
func formatsTitle(_ title: String, expectedSlug: String) {
    let result = SlugFormatter().format(title)
    #expect(result == expectedSlug)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;zip&lt;/code&gt; pairs the first title with the first expected slug, the second title with the second expected slug and so on. This gives us the same three test cases without introducing &lt;code&gt;SlugTestCase&lt;/code&gt;. However, the two collections need to stay in the same order. A dedicated type keeps each input visibly next to its expected output and scales better when a test needs more values.&lt;/p&gt;&lt;h3&gt;Testing more than one collection of arguments&lt;/h3&gt;&lt;p&gt;We can also pass multiple collections to a parameterized test. Unlike &lt;code&gt;zip&lt;/code&gt;, Swift Testing then uses the Cartesian product: every value from the first collection is paired with every value from the second.&lt;/p&gt;&lt;p&gt;This is useful when the same expectation applies to every combination. For example, suppose both editors and administrators should be able to edit every supported content type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UserRole {
    case editor
    case administrator
}

enum ContentType {
    case article
    case video
}

@Test(
    arguments: [UserRole.editor, .administrator],
               [ContentType.article, .video]
)
func authorizedRoleCanEdit(_ role: UserRole, contentType: ContentType) {
    let result = Permissions().canEdit(contentType, as: role)
    #expect(result == true)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With two roles and two content types, Swift Testing creates four test cases. The expected result is explicit and doesn&amp;#x27;t need to be derived inside the test. If only particular roles and content types should be paired, &lt;code&gt;zip&lt;/code&gt; or a dedicated test-case type would be a better fit than a Cartesian product.&lt;/p&gt;&lt;h3&gt;Organizing tests in suites&lt;/h3&gt;&lt;p&gt;Any Swift type that contains test functions acts as a test suite. We don&amp;#x27;t need to add &lt;code&gt;@Suite&lt;/code&gt; when the type name already describes the group clearly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SlugFormatterTests {
    @Test func formatsTitles() { ... }
    @Test func usesCustomSeparators() { ... }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, the suite groups two related behaviors of &lt;code&gt;SlugFormatter&lt;/code&gt;, and that hierarchy is reflected in Xcode&amp;#x27;s Test navigator. As the suite grows, we can nest additional suite types to separate meaningful groups such as default formatting and custom separators.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;@Suite&lt;/code&gt; attribute becomes useful when we want to apply traits to the whole group or need a custom display name that adds information beyond the type name. We shouldn&amp;#x27;t add it only to repeat a name that is already clear in code.&lt;/p&gt;&lt;h3&gt;Grouping related tests with tags&lt;/h3&gt;&lt;p&gt;Suites create a source-level hierarchy: every test belongs to one place in that hierarchy. Tags provide an additional way to group tests across files and suites.&lt;/p&gt;&lt;p&gt;We first declare the tags as static members of &lt;code&gt;Tag&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension Tag {
    @Tag static var formatting: Self
    @Tag static var critical: Self
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then apply them to an individual test:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Test(
    .tags(.formatting, .critical),
    arguments: SlugTestCase.examples
)
func formatsTitle(_ testCase: SlugTestCase) {
    let result = SlugFormatter().format(testCase.title)

    #expect(result == testCase.expectedSlug)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or we can apply a tag to a suite. Its tests and nested suites inherit that tag:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Suite(.tags(.formatting))
struct SlugFormatterTests {
    // All tests in this suite inherit the formatting tag.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Tags are useful for categories that cross the suite hierarchy, such as critical tests, tests for a particular feature or tests that depend on a certain resource. Xcode can display and run tests by tag from the Test navigator.&lt;/p&gt;&lt;h3&gt;Accounting for parallel execution&lt;/h3&gt;&lt;p&gt;Swift Testing runs tests in parallel by default, including the individual cases of a parameterized test. This makes a large test suite faster, but it also means test cases shouldn&amp;#x27;t mutate shared state or rely on a particular execution order.&lt;/p&gt;&lt;p&gt;Swift Testing doesn&amp;#x27;t create one suite instance and call all test methods on it. For every test case, it first creates a new suite instance and then invokes the test method on that instance. In a parameterized test, this happens for every argument.&lt;/p&gt;&lt;p&gt;This means that &lt;code&gt;formatter&lt;/code&gt; is initialized again for each test case in the following example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SlugFormatterTests {
    let formatter = SlugFormatter()

    @Test(arguments: SlugTestCase.examples)
    func formatsTitle(_ testCase: SlugTestCase) {
        #expect(
            formatter.format(testCase.title) == testCase.expectedSlug
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Independent test data is preferable to disabling parallelization. When tests must share a resource that can&amp;#x27;t safely be isolated, we can apply the &lt;code&gt;.serialized&lt;/code&gt; trait:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Suite(.serialized)
struct DatabaseMigrationTests {
    // Tests in this suite run one after another.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Applied to a suite, &lt;code&gt;.serialized&lt;/code&gt; also affects its nested suites and parameterized test cases. It should be used deliberately: serialization can hide shared-state problems and slow the test suite, so isolating dependencies is usually the better long-term solution.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Parameterized tests let us describe a behavior once and verify it against many inputs. Each argument still becomes its own test case, so we keep focused diagnostics without duplicating the test logic.&lt;/p&gt;&lt;p&gt;For a maintainable test suite, we can use the different organization tools for different purposes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Parameterized tests cover multiple inputs for the same behavior.&lt;/li&gt;&lt;li&gt;Suites group tests by feature or responsibility.&lt;/li&gt;&lt;li&gt;Nested suites divide a larger feature into meaningful behaviors.&lt;/li&gt;&lt;li&gt;Tags create groups that span multiple suites and files.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Together, these tools keep the suite easier to navigate while allowing us to add edge cases simply by extending an argument collection.&lt;/p&gt;</content:encoded><category>testing</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Building responsive and reliable search in SwiftUI]]></title><description><![CDATA[Learn how to debounce search input, cancel obsolete requests and prevent stale results using Swift concurrency.]]></description><link>https://tanaschita.com/swiftui-responsive-race-free-debounce-search</link><guid isPermaLink="false">https://tanaschita.com/swiftui-responsive-race-free-debounce-search</guid><pubDate>Sun, 13 Sep 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building a search feature, we usually want results to update as the user types. To keep this interaction responsive and robust, we need to avoid sending unnecessary requests and ensure that only results for the current query reach the interface.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll use SwiftUI and Swift concurrency to debounce search input, cancel obsolete requests and prevent older responses from replacing newer results.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;We&amp;#x27;ll build the solution in four steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Connect the search task to the current query.&lt;/li&gt;&lt;li&gt;Debounce the input before starting a request.&lt;/li&gt;&lt;li&gt;Prevent obsolete requests from publishing stale results.&lt;/li&gt;&lt;li&gt;Keep the loading and error states tied to the current search.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Step 1: Connecting the search task to the query&lt;/h3&gt;&lt;p&gt;A first implementation might start a new task whenever the query changes:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SearchView: View {
    @State private var query = &amp;quot;&amp;quot;
    @State private var results: [SearchResult] = []

    let searchService: SearchService

    var body: some View {
        List(results) { result in
            Text(result.title)
        }
        .searchable(text: $query)
        .onChange(of: query) { _, newQuery in
            Task {
                results = try await searchService.search(for: newQuery)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This starts an independent task for every change. Suppose the user types &amp;quot;cat&amp;quot;, pauses briefly, and then continues with &amp;quot;cats&amp;quot;. The request for &amp;quot;cats&amp;quot; might finish first, while the slower request for &amp;quot;cat&amp;quot; finishes afterward and overwrites the newer results.&lt;/p&gt;&lt;p&gt;This is an ordering race between asynchronous operations. The problem is that the final value depends on which request finishes last rather than which query is current.&lt;/p&gt;&lt;p&gt;To avoid leaving these independent tasks running, we can use SwiftUI&amp;#x27;s &lt;code&gt;task(id:)&lt;/code&gt; modifier. It gives the search operation a lifecycle tied to the query:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.task(id: query) {
    results = try await searchService.search(for: query)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SwiftUI starts the task when the view appears. Whenever &lt;code&gt;query&lt;/code&gt; changes, it cancels the previous task and starts a new one with the updated value. It also cancels the task when the view disappears.&lt;/p&gt;&lt;h3&gt;Step 2: Debouncing the input&lt;/h3&gt;&lt;p&gt;Without a debounce, every keystroke can start a request, even though the user hasn&amp;#x27;t finished entering the query yet. A debounce avoids this unnecessary work by waiting for a short period of inactivity before performing the search. We can implement it by sleeping at the start of the task:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.task(id: query) {
    do {
        try await Task.sleep(for: .milliseconds(300))
        results = try await searchService.search(for: query)
    } catch is CancellationError {
        // Expected when the query changes.
    } catch {
        // Handle the search error.
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;Task.sleep(for:)&lt;/code&gt; responds to cancellation by throwing &lt;code&gt;CancellationError&lt;/code&gt;. If the user types another character during those 300 milliseconds, SwiftUI cancels the sleeping task before it reaches the request. Only after the user pauses does the search begin.&lt;/p&gt;&lt;p&gt;This gives us debounce behavior using the task lifecycle that SwiftUI already manages. If an app already uses Combine, its &lt;code&gt;debounce&lt;/code&gt; operator remains a valid alternative, as shown in the article on &lt;a href=&quot;/combine-swiftui-search-query-debounce/&quot;&gt;debouncing a SwiftUI search query with Combine&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Step 3: Preventing stale results&lt;/h3&gt;&lt;p&gt;When &lt;code&gt;query&lt;/code&gt; changes, &lt;code&gt;task(id:)&lt;/code&gt; requests cancellation of the previous task by marking it as cancelled. It doesn&amp;#x27;t forcibly terminate the task at that exact point. Cancellation in Swift is cooperative: the code running inside the task needs to notice the request and respond to it.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Task.sleep(for:)&lt;/code&gt; notices cancellation and throws, which is why it works for our debounce. Many system APIs, including URLSession&amp;#x27;s async methods, behave similarly. When &lt;code&gt;search(for:)&lt;/code&gt; uses only cancellation-aware operations, it will throw and the old task won&amp;#x27;t reach the result assignment.&lt;/p&gt;&lt;p&gt;However, our search service might perform additional work, bridge an older callback API or catch a cancellation error internally. It could then return a value even though SwiftUI has already cancelled its task. &lt;code&gt;Task.checkCancellation()&lt;/code&gt; provides an explicit checkpoint before we update the interface:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let newResults = try await searchService.search(for: submittedQuery)

try Task.checkCancellation()
results = newResults
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;Task.checkCancellation()&lt;/code&gt; reads the cancellation state set by &lt;code&gt;task(id:)&lt;/code&gt; and throws if this task is obsolete. It makes sure that an earlier cancellation request is respected before the task publishes its result. If we know that the complete search implementation always propagates cancellation, this additional check is defensive rather than strictly necessary.&lt;/p&gt;&lt;p&gt;We can add a second, explicit guard by capturing the query associated with the request and comparing it with the current query before publishing the result:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let submittedQuery = query
let newResults = try await searchService.search(for: submittedQuery)

try Task.checkCancellation()

guard submittedQuery == query else { return }

results = newResults
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With &lt;code&gt;task(id:)&lt;/code&gt; and a cancellation check, this comparison is usually redundant. It still documents an important rule at the point where shared UI state changes: a response may only update the results when it belongs to the current query. This is particularly useful if the surrounding task management changes later.&lt;/p&gt;&lt;h3&gt;Step 4: Managing loading and error states&lt;/h3&gt;&lt;p&gt;We can now combine empty-query handling, debouncing, cancellation and relevance checking in one method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SearchView: View {
    @State private var query = &amp;quot;&amp;quot;
    @State private var results: [SearchResult] = []
    @State private var isLoading = false
    @State private var errorMessage: String?

    let searchService: SearchService

    var body: some View {
        Group {
            if isLoading {
                ProgressView()
            } else if let errorMessage {
                ContentUnavailableView(
                    &amp;quot;Search failed&amp;quot;,
                    systemImage: &amp;quot;exclamationmark.triangle&amp;quot;,
                    description: Text(errorMessage)
                )
            } else {
                List(results) { result in
                    Text(result.title)
                }
            }
        }
        .searchable(text: $query)
        .task(id: query) {
            await performSearch(for: query)
        }
    }

    private func performSearch(for rawQuery: String) async {
        let submittedQuery = rawQuery.trimmingCharacters(in: .whitespaces)

        isLoading = false
        errorMessage = nil

        guard !submittedQuery.isEmpty else {
            results = []
            return
        }

        do {
            try await Task.sleep(for: .milliseconds(300))

            isLoading = true
            let newResults = try await searchService.search(for: submittedQuery)

            try Task.checkCancellation()
            guard submittedQuery == query.trimmingCharacters(in: .whitespaces) else {
                return
            }

            results = newResults
            isLoading = false
        } catch is CancellationError {
            // A new task now owns the loading and result state.
        } catch {
            guard submittedQuery == query.trimmingCharacters(in: .whitespaces) else {
                return
            }

            errorMessage = error.localizedDescription
            isLoading = false
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We set &lt;code&gt;isLoading&lt;/code&gt; only after the debounce finishes, so quickly typing doesn&amp;#x27;t flash a progress indicator for every character. At the beginning of each new task, we reset the loading and error state left by the previous query.&lt;/p&gt;&lt;p&gt;The cancellation handler deliberately doesn&amp;#x27;t change any UI state. By the time an old task observes cancellation, a newer task may already be responsible for that state. Letting the old task set &lt;code&gt;isLoading&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, for example, could hide the progress indicator while the current request is still running.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Besides debouncing requests while the user is typing, responsive search requires us to control which asynchronous operation is allowed to update the interface.&lt;/p&gt;&lt;p&gt;By connecting the work to &lt;code&gt;query&lt;/code&gt; with &lt;code&gt;task(id:)&lt;/code&gt;, using a cancellable sleep as a debounce and validating the response before publishing it, we can keep the implementation compact while protecting it from stale results.&lt;/p&gt;&lt;p&gt;The same approach works whenever input can change faster than asynchronous work completes, such as autocomplete suggestions, remote form validation or filters that load data from a server. We can tie the task to the changing input, cancel obsolete work and verify its relevance before updating the UI.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>concurrency</category><category>async/await</category><category>ios</category></item><item><title><![CDATA[Getting started with Live Activities in SwiftUI]]></title><description><![CDATA[Build a public transport journey tracker for the Lock Screen and Dynamic Island with ActivityKit.]]></description><link>https://tanaschita.com/activitykit-live-activities-swiftui</link><guid isPermaLink="false">https://tanaschita.com/activitykit-live-activities-swiftui</guid><pubDate>Sun, 06 Sep 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Live Activities let users follow an ongoing event without repeatedly opening our app. They can show a public transport journey, a workout, an order&amp;#x27;s delivery status or any other information that changes over a limited period of time.&lt;/p&gt;&lt;p&gt;Although their interface is part of a widget extension, Live Activities are not regular widgets. They don&amp;#x27;t use a timeline provider. Instead, we start, update and end them from our app with ActivityKit, while WidgetKit and SwiftUI define how their current state is presented.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll build a small public transport journey tracker and update it locally from the app. This lets us focus on the basic structure before adding remote updates with push notifications.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding a Live Activity to the project&lt;/h3&gt;&lt;p&gt;A Live Activity&amp;#x27;s SwiftUI interface lives in a widget extension. If the app doesn&amp;#x27;t have one yet, we can add a &lt;code&gt;Widget Extension&lt;/code&gt; target in Xcode and select &lt;code&gt;Include Live Activity&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We also need to enable Live Activities for the app target. In the target&amp;#x27;s Info settings, add &lt;code&gt;Supports Live Activities&lt;/code&gt; and set it to &lt;code&gt;YES&lt;/code&gt;. The corresponding Info.plist key is &lt;code&gt;NSSupportsLiveActivities&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Separating fixed and changing data&lt;/h3&gt;&lt;p&gt;Every Live Activity has an attributes type. Its stored properties describe information that stays fixed for the lifetime of the activity. A nested &lt;code&gt;ContentState&lt;/code&gt; contains the values that can change:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ActivityKit

struct JourneyAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var currentStop: String
        var nextStop: String?
        var progress: Double
    }

    let line: String
    let destination: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The app and widget extension both need access to &lt;code&gt;JourneyAttributes&lt;/code&gt;. We can place it in a separate Swift file and enable target membership for both targets.&lt;/p&gt;&lt;p&gt;The line and destination don&amp;#x27;t change during the journey, so they belong to &lt;code&gt;JourneyAttributes&lt;/code&gt;. The current stop, next stop and progress will be replaced whenever the activity is updated, so they belong to &lt;code&gt;ContentState&lt;/code&gt;. The next stop is optional because there isn&amp;#x27;t one after we reach the destination.&lt;/p&gt;&lt;p&gt;Apple limits the combined size of a Live Activity&amp;#x27;s static and dynamic data to 4 KB. The attributes and content state should therefore contain only compact values required to render the interface.&lt;/p&gt;&lt;h3&gt;Building the Live Activity interface&lt;/h3&gt;&lt;p&gt;Now that we&amp;#x27;ve defined the data, we need to tell the system how the Live Activity should look. This interface belongs to the widget extension and is described by an &lt;code&gt;ActivityConfiguration&lt;/code&gt;. We keep the configuration itself small by moving the Lock Screen and Dynamic Island presentations into separate helpers:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ActivityKit
import SwiftUI
import WidgetKit

struct JourneyLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: JourneyAttributes.self) { context in
            JourneyLockScreenView(context: context)
        } dynamicIsland: { context in
            makeDynamicIsland(for: context)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first closure builds the presentation used on the Lock Screen. Its &lt;code&gt;context&lt;/code&gt; gives us access to both the fixed attributes and the current state. We pass that context to a dedicated view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct JourneyLockScreenView: View {
    let context: ActivityViewContext&amp;lt;JourneyAttributes&amp;gt;

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                Label(context.attributes.line, systemImage: &amp;quot;tram.fill&amp;quot;)
                Spacer()
                Text(context.attributes.destination)
                    .font(.callout.bold())
            }

            VStack(alignment: .leading, spacing: 4) {
                Text(&amp;quot;Current: \(context.state.currentStop)&amp;quot;)

                if let nextStop = context.state.nextStop {
                    Text(&amp;quot;Next: \(nextStop)&amp;quot;)
                        .foregroundStyle(.secondary)
                } else {
                    Text(&amp;quot;Arrived&amp;quot;)
                        .foregroundStyle(.secondary)
                }
            }

            ProgressView(value: context.state.progress)
                .tint(.green)
        }
        .padding()
        .foregroundStyle(.black)
        .activityBackgroundTint(.white)
        .activitySystemActionForegroundColor(.green)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Building the Dynamic Island presentations&lt;/h3&gt;&lt;p&gt;The Dynamic Island is the interactive area around the front-facing camera and sensors on supported iPhones. A Live Activity can use this area to show compact information while another app is open, and users can touch and hold it to reveal a larger presentation. On iPhones without a Dynamic Island, the Live Activity is still available on the Lock Screen.&lt;/p&gt;&lt;p&gt;The second closure must return a &lt;code&gt;DynamicIsland&lt;/code&gt;. We can move it into a method on our widget:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private extension JourneyLiveActivity {
    func makeDynamicIsland(
        for context: ActivityViewContext&amp;lt;JourneyAttributes&amp;gt;
    ) -&amp;gt; DynamicIsland {
        DynamicIsland {
            DynamicIslandExpandedRegion(.leading) {
                Label(context.attributes.line, systemImage: &amp;quot;tram.fill&amp;quot;)
                    .font(.headline)
            }

            DynamicIslandExpandedRegion(.trailing) {
                Text(context.state.progress, format: .percent.precision(.fractionLength(0)))
            }

            DynamicIslandExpandedRegion(.bottom) {
                VStack(alignment: .leading) {
                    Text(context.state.currentStop)

                    if let nextStop = context.state.nextStop {
                        Text(&amp;quot;Next: \(nextStop)&amp;quot;)
                            .foregroundStyle(.secondary)
                    } else {
                        Text(&amp;quot;Arrived at \(context.attributes.destination)&amp;quot;)
                            .foregroundStyle(.secondary)
                    }
                }
            }
        } compactLeading: {
            Text(context.attributes.line)
        } compactTrailing: {
            Text(context.state.progress, format: .percent.precision(.fractionLength(0)))
        } minimal: {
            Image(systemName: &amp;quot;tram.fill&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The Dynamic Island defines three additional presentations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The expanded presentation appears when the user touches and holds the Dynamic Island.&lt;/li&gt;&lt;li&gt;The compact leading and trailing views appear on either side of the camera area.&lt;/li&gt;&lt;li&gt;The minimal presentation is used when more than one Live Activity is active.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If the extension uses a &lt;code&gt;WidgetBundle&lt;/code&gt;, we need to add &lt;code&gt;JourneyLiveActivity()&lt;/code&gt; to its body just like any other widget configuration.&lt;/p&gt;&lt;h3&gt;Starting a Live Activity&lt;/h3&gt;&lt;p&gt;We start a Live Activity from the app while it is in the foreground. For this local example, a view can keep a reference to the returned activity:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ActivityKit
import SwiftUI

struct ContentView: View {
    @State private var activity: Activity&amp;lt;JourneyAttributes&amp;gt;?

    var body: some View {
        VStack(spacing: 16) {
            Button(&amp;quot;Start journey&amp;quot;) {
                startJourney()
            }
            .disabled(activity != nil)

            Button(&amp;quot;Next stop&amp;quot;) {
                Task {
                    await updateJourney()
                }
            }
            .disabled(activity == nil)

            Button(&amp;quot;Arrive&amp;quot;) {
                Task {
                    await endJourney()
                }
            }
            .disabled(activity == nil)
        }
        .buttonStyle(.borderedProminent)
        .padding()
    }

    private func startJourney() {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            return
        }

        let attributes = JourneyAttributes(
            line: &amp;quot;U8&amp;quot;,
            destination: &amp;quot;Alexanderplatz&amp;quot;
        )
        let state = JourneyAttributes.ContentState(
            currentStop: &amp;quot;Hermannplatz&amp;quot;,
            nextStop: &amp;quot;Kottbusser Tor&amp;quot;,
            progress: 0.15
        )
        let content = ActivityContent(state: state, staleDate: nil)

        do {
            activity = try Activity.request(
                attributes: attributes,
                content: content,
                pushType: nil
            )
        } catch {
            print(&amp;quot;Couldn&amp;#x27;t start journey: \(error)&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ActivityAuthorizationInfo&lt;/code&gt; tells us whether the user allows Live Activities. &lt;code&gt;Activity.request&lt;/code&gt; creates one with its fixed attributes and its initial content. Passing &lt;code&gt;nil&lt;/code&gt; as the push type is enough because this example only sends updates from the app.&lt;/p&gt;&lt;h3&gt;Updating the current state&lt;/h3&gt;&lt;p&gt;To update the presentation, we create a new content state and pass it to the activity:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private func updateJourney() async {
    guard let activity else {
        return
    }

    let state = JourneyAttributes.ContentState(
        currentStop: &amp;quot;Kottbusser Tor&amp;quot;,
        nextStop: &amp;quot;Moritzplatz&amp;quot;,
        progress: 0.4
    )
    let content = ActivityContent(state: state, staleDate: nil)

    await activity.update(content)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The system renders the Live Activity again with the new &lt;code&gt;context.state&lt;/code&gt;. Updates aren&amp;#x27;t a way to run arbitrary code inside the widget extension: the extension receives the state and uses it to build the view.&lt;/p&gt;&lt;p&gt;For data that can become outdated, we can pass a &lt;code&gt;staleDate&lt;/code&gt;. After that date, ActivityKit considers the content stale and makes this available to the presentation through &lt;code&gt;context.isStale&lt;/code&gt;. The stale date doesn&amp;#x27;t fetch new data automatically.&lt;/p&gt;&lt;h3&gt;Ending the Live Activity&lt;/h3&gt;&lt;p&gt;A Live Activity represents a temporary event, so we should explicitly end it when that event is finished. We also provide a final state because the system may leave the ended activity visible on the Lock Screen for a while:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private func endJourney() async {
    guard let activity else {
        return
    }

    let finalState = JourneyAttributes.ContentState(
        currentStop: &amp;quot;Alexanderplatz&amp;quot;,
        nextStop: nil,
        progress: 1
    )
    let finalContent = ActivityContent(
        state: finalState,
        staleDate: nil
    )

    await activity.end(
        finalContent,
        dismissalPolicy: .after(Date.now.addingTimeInterval(60))
    )

    self.activity = nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The dismissal policy controls how long the completed activity remains visible. We can use &lt;code&gt;.immediate&lt;/code&gt;, &lt;code&gt;.default&lt;/code&gt;, or &lt;code&gt;.after(_:)&lt;/code&gt;. Here, it stays on the Lock Screen for one minute so the user can see that the destination has been reached.&lt;/p&gt;&lt;p&gt;In a larger app, we shouldn&amp;#x27;t rely only on an in-memory reference. &lt;code&gt;Activity&amp;lt;JourneyAttributes&amp;gt;.activities&lt;/code&gt; gives us the currently running activities of this type, which lets us find them again after the app has been relaunched.&lt;/p&gt;&lt;h3&gt;Local and remote updates&lt;/h3&gt;&lt;p&gt;This example updates the Live Activity while code in our app is running. The Live Activity itself can&amp;#x27;t start a network request or continuously execute in the background.&lt;/p&gt;&lt;p&gt;For a real public transport journey, service and location updates would usually originate outside our app while it isn&amp;#x27;t running. ActivityKit supports this through specialized push notifications. That requires obtaining a push token and sending correctly structured updates through APNs, so it is best treated as a separate step after the local lifecycle is working.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;A Live Activity consists of two closely related parts: an &lt;code&gt;ActivityAttributes&lt;/code&gt; model shared between the targets, and an &lt;code&gt;ActivityConfiguration&lt;/code&gt; in the widget extension that renders it. The app then uses ActivityKit to start the activity, replace its dynamic content and end it with a final state.&lt;/p&gt;&lt;p&gt;Once this local flow works, we can build on it with push updates, deep links and interactive controls without changing the core structure.&lt;/p&gt;</content:encoded><category>activitykit</category><category>widgetkit</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Managing toolbar visibility and overflow in SwiftUI]]></title><description><![CDATA[Learn how to prioritize, pin and move SwiftUI toolbar actions into an overflow menu.]]></description><link>https://tanaschita.com/swiftui-toolbar-visibility-overflow</link><guid isPermaLink="false">https://tanaschita.com/swiftui-toolbar-visibility-overflow</guid><pubDate>Sun, 30 Aug 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When a SwiftUI toolbar contains more actions than the available space can hold, the system decides which items remain visible and which ones move into the overflow menu. This becomes especially important when our app can run at different window sizes.&lt;/p&gt;&lt;p&gt;Starting with iOS 27, SwiftUI adds several APIs that let us communicate which actions matter most, which ones belong in the overflow menu, and which one should always remain visible.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;If you&amp;#x27;re new to creating toolbars and placing their items, you can start with this &lt;a href=&quot;/swiftui-toolbars-guide/&quot;&gt;guide to toolbars in SwiftUI&lt;/a&gt;. In this article, we&amp;#x27;ll focus specifically on making an existing toolbar adapt to limited space.&lt;/p&gt;&lt;h3&gt;Prioritizing important toolbar items&lt;/h3&gt;&lt;p&gt;By default, SwiftUI manages the visibility of toolbar items automatically. We can use the &lt;code&gt;visibilityPriority&lt;/code&gt; modifier to tell the system which actions should remain visible for longer as the available space becomes smaller:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItemGroup {
        Button(&amp;quot;Undo&amp;quot;, systemImage: &amp;quot;arrow.uturn.backward&amp;quot;, action: undo)
        Button(&amp;quot;Redo&amp;quot;, systemImage: &amp;quot;arrow.uturn.forward&amp;quot;, action: redo)
    }
    .visibilityPriority(.high)

    ToolbarItem {
        Button(&amp;quot;Add&amp;quot;, systemImage: &amp;quot;plus&amp;quot;, action: addItem)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, SwiftUI prioritizes the Undo and Redo group over the Add button when all items no longer fit.&lt;/p&gt;&lt;p&gt;The available values are &lt;code&gt;.low&lt;/code&gt;, &lt;code&gt;.automatic&lt;/code&gt; and &lt;code&gt;.high&lt;/code&gt;. A priority expresses the relative importance of an item; it does not reserve a fixed position or guarantee that the item is always visible.&lt;/p&gt;&lt;h3&gt;Placing secondary actions in the overflow menu&lt;/h3&gt;&lt;p&gt;Some actions do not need to take up toolbar space at all. We can put them inside a &lt;code&gt;ToolbarOverflowMenu&lt;/code&gt; to make them available from the overflow menu regardless of how much room the toolbar has:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarOverflowMenu {
        Button(&amp;quot;Duplicate&amp;quot;, systemImage: &amp;quot;plus.square.on.square&amp;quot;, action: duplicate)
        Button(&amp;quot;Export&amp;quot;, systemImage: &amp;quot;square.and.arrow.up&amp;quot;, action: export)
        Button(&amp;quot;Delete&amp;quot;, systemImage: &amp;quot;trash&amp;quot;, role: .destructive, action: delete)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is useful for secondary or less frequently used actions.&lt;/p&gt;&lt;h3&gt;Keeping a critical action visible&lt;/h3&gt;&lt;p&gt;For an action that should remain visible at all times, we can use the &lt;code&gt;.topBarPinnedTrailing&lt;/code&gt; placement:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItem(placement: .topBarPinnedTrailing) {
        Button(&amp;quot;Share&amp;quot;, systemImage: &amp;quot;square.and.arrow.up&amp;quot;, action: share)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The pinned item stays at the trailing edge of the top bar and does not move into the overflow menu.&lt;/p&gt;&lt;h3&gt;Minimizing the toolbar while scrolling&lt;/h3&gt;&lt;p&gt;We can also give scrollable content more room by minimizing the navigation bar when the user scrolls down:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ScrollView {
    LazyVStack {
        // Content
    }
}
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this modifier, the navigation bar moves out of the way while scrolling down, and SwiftUI manages when it reappears. This behavior is independent of the visibility and overflow configuration of the toolbar items themselves.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Together, the toolbar APIs let us express the importance of each action while SwiftUI continues to handle the toolbar&amp;#x27;s layout across different sizes.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Presenting alerts and confirmation dialogs from identifiable data in SwiftUI]]></title><description><![CDATA[Learn how to present alerts and confirmation dialogs using optional identifiable items instead of Boolean state.]]></description><link>https://tanaschita.com/swiftui-alert-identifiable-data</link><guid isPermaLink="false">https://tanaschita.com/swiftui-alert-identifiable-data</guid><pubDate>Sun, 23 Aug 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One possibility to control SwiftUI alerts and confirmation dialogs is with a Boolean binding. Starting with iOS 27, item-based alerts and confirmation dialogs are supported and provide an alternative by using an optional identifiable value as both the presentation state and its data.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look how to use it.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Presenting an alert with a Boolean binding&lt;/h3&gt;&lt;p&gt;Imagine we want to ask for confirmation before deleting a task. A Boolean-based implementation could look like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct TasksView: View {
    @State private var taskToDelete: Task?
    @State private var isShowingDeleteAlert = false

    var body: some View {
        List(tasks) { task in
            Button(&amp;quot;Delete \(task.title)&amp;quot;) {
                taskToDelete = task
                isShowingDeleteAlert = true
            }
        }
        .alert(&amp;quot;Delete task?&amp;quot;, isPresented: $isShowingDeleteAlert) {
            Button(&amp;quot;Delete&amp;quot;, role: .destructive) {
                if let taskToDelete {
                    delete(taskToDelete)
                }
            }

            Button(&amp;quot;Cancel&amp;quot;, role: .cancel) {}
        } message: {
            if let taskToDelete {
                Text(&amp;quot;Are you sure you want to delete \(taskToDelete.title)?&amp;quot;)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This requires two pieces of state: one to decide whether to present the alert and another to hold the task. Because &lt;code&gt;taskToDelete&lt;/code&gt; is optional, we also need to unwrap it in both the action and the message. These values represent the same event, but they can get out of sync.&lt;/p&gt;&lt;h3&gt;Presenting an alert from an identifiable item&lt;/h3&gt;&lt;p&gt;When the data conforms to &lt;code&gt;Identifiable&lt;/code&gt;, we can instead pass a binding to the optional item directly to the &lt;code&gt;alert&lt;/code&gt; modifier:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Task: Identifiable {
    let id = UUID()
    let title: String
}

struct TasksView: View {
    @State private var taskToDelete: Task?

    var body: some View {
        List(tasks) { task in
            Button(&amp;quot;Delete \(task.title)&amp;quot;) {
                taskToDelete = task
            }
        }
        .alert(&amp;quot;Delete task?&amp;quot;, item: $taskToDelete) { task in
            Button(&amp;quot;Delete&amp;quot;, role: .destructive) {
                delete(task)
            }

            Button(&amp;quot;Cancel&amp;quot;, role: .cancel) {}
        } message: { task in
            Text(&amp;quot;Are you sure you want to delete \(task.title)?&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Setting &lt;code&gt;taskToDelete&lt;/code&gt; to a value presents the alert. SwiftUI passes that value into the actions and message closures, so we can use the selected task without optional unwrapping. When the alert is dismissed, SwiftUI sets the binding back to &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;We now have a single source of truth:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;taskToDelete == nil&lt;/code&gt; means that the alert is not presented.&lt;/li&gt;&lt;li&gt;A task stored in &lt;code&gt;taskToDelete&lt;/code&gt; means that the alert is presented for that task.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This makes item-based presentation especially useful for alerts whose title, message, or actions depend on selected data.&lt;/p&gt;&lt;h3&gt;Using the same approach with confirmation dialogs&lt;/h3&gt;&lt;p&gt;Confirmation dialogs support the same item-binding pattern. We only need to replace the &lt;code&gt;alert&lt;/code&gt; modifier:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.confirmationDialog(
    &amp;quot;Delete task?&amp;quot;,
    item: $taskToDelete,
    titleVisibility: .visible
) { task in
    Button(&amp;quot;Delete&amp;quot;, role: .destructive) {
        delete(task)
    }
} message: { task in
    Text(&amp;quot;Are you sure you want to delete \(task.title)?&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The item-based overloads for alerts and confirmation dialogs are available starting with iOS 27. For earlier versions, we still need to use the Boolean-based APIs.&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Adding swipe actions to any SwiftUI scroll view]]></title><description><![CDATA[Learn how to use swipe actions outside of List in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-scrollview-swipe-actions</link><guid isPermaLink="false">https://tanaschita.com/swiftui-scrollview-swipe-actions</guid><pubDate>Sun, 16 Aug 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Until iOS 27, SwiftUI&amp;#x27;s &lt;code&gt;swipeActions&lt;/code&gt; modifier worked with rows inside a &lt;code&gt;List&lt;/code&gt;. Starting with iOS 27, we can also add swipe actions to views inside a &lt;code&gt;ScrollView&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding swipe actions to a List&lt;/h3&gt;&lt;p&gt;In a &lt;code&gt;List&lt;/code&gt;, we attach the &lt;code&gt;swipeActions&lt;/code&gt; modifier directly to each row:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;List {
    ForEach(tasks) { task in
        TaskRow(task: task)
            .swipeActions {
                Button(&amp;quot;Delete&amp;quot;, systemImage: &amp;quot;trash&amp;quot;, role: .destructive) {
                    delete(task)
                }
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;List&lt;/code&gt; provides the container behavior needed to reveal the action when the user swipes the row. Applying the same row modifier inside a regular &lt;code&gt;ScrollView&lt;/code&gt;, however, is not enough.&lt;/p&gt;&lt;h3&gt;Enabling swipe actions in a ScrollView&lt;/h3&gt;&lt;p&gt;For a custom scroll view, we additionally apply the new &lt;code&gt;swipeActionsContainer&lt;/code&gt; modifier to the &lt;code&gt;ScrollView&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ScrollView {
    LazyVStack(spacing: 12) {
        ForEach(tasks) { task in
            TaskRow(task: task)
                .swipeActions {
                    Button(&amp;quot;Delete&amp;quot;, systemImage: &amp;quot;trash&amp;quot;, role: .destructive) {
                        delete(task)
                    }
                }
        }
    }
    .padding()
}
.swipeActionsContainer()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two modifiers have different responsibilities: &lt;code&gt;swipeActions&lt;/code&gt; defines the actions for an individual view, while &lt;code&gt;swipeActionsContainer&lt;/code&gt; enables the interaction for the surrounding scroll view.&lt;/p&gt;&lt;h3&gt;Customizing swipe actions&lt;/h3&gt;&lt;p&gt;The existing options for swipe actions work in the new container as well. For example, we can add actions to both edges and control whether a full swipe performs the first action:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;TaskRow(task: task)
    .swipeActions(edge: .leading, allowsFullSwipe: false) {
        Button(&amp;quot;Favorite&amp;quot;, systemImage: &amp;quot;star&amp;quot;) {
            favorite(task)
        }
        .tint(.yellow)
    }
    .swipeActions(edge: .trailing) {
        Button(&amp;quot;Delete&amp;quot;, systemImage: &amp;quot;trash&amp;quot;, role: .destructive) {
            delete(task)
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The action labels, roles, tints, edges, and full-swipe behavior are configured just as they are for a &lt;code&gt;List&lt;/code&gt; row. The only new part is marking the parent &lt;code&gt;ScrollView&lt;/code&gt; as a swipe actions container.&lt;/p&gt;&lt;p&gt;With this small addition, we can keep native swipe interactions while using &lt;code&gt;LazyVStack&lt;/code&gt; or another custom scrollable layout.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Managing focus in SwiftUI with FocusState]]></title><description><![CDATA[Learn how to observe, move, and remove focus in SwiftUI views.]]></description><link>https://tanaschita.com/swiftui-focus-state</link><guid isPermaLink="false">https://tanaschita.com/swiftui-focus-state</guid><pubDate>Sun, 09 Aug 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI provides the &lt;code&gt;@FocusState&lt;/code&gt; property wrapper to observe and control focus in a view hierarchy. We can use it to focus a text field programmatically, move focus between fields, or remove focus to dismiss the keyboard.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at the different ways to represent focus and how to pass focus state between views.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Managing focus for a single view&lt;/h3&gt;&lt;p&gt;When we only need to manage one focusable view, we can represent its focus state with a &lt;code&gt;Bool&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct NameView: View {
    @State private var name = &amp;quot;&amp;quot;
    @FocusState private var isNameFocused: Bool

    var body: some View {
        VStack {
            TextField(&amp;quot;Name&amp;quot;, text: $name)
                .focused($isNameFocused)

            Button(&amp;quot;Edit name&amp;quot;) {
                isNameFocused = true
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;focused(_:)&lt;/code&gt; modifier connects the text field to &lt;code&gt;isNameFocused&lt;/code&gt;. The value changes to &lt;code&gt;true&lt;/code&gt; when the text field receives focus and back to &lt;code&gt;false&lt;/code&gt; when focus leaves it.&lt;/p&gt;&lt;p&gt;This connection also works in the other direction. Setting the value to &lt;code&gt;true&lt;/code&gt; moves focus to the text field. Setting it to &lt;code&gt;false&lt;/code&gt; removes focus.&lt;/p&gt;&lt;h3&gt;Managing focus for multiple views&lt;/h3&gt;&lt;p&gt;For multiple focusable views, a single Boolean is no longer enough because it cannot describe which view is focused. We can use an optional enum instead:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct LoginView: View {
    private enum Field: Hashable {
        case email
        case password
    }

    @State private var email = &amp;quot;&amp;quot;
    @State private var password = &amp;quot;&amp;quot;
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            TextField(&amp;quot;Email&amp;quot;, text: $email)
                .textContentType(.emailAddress)
                .submitLabel(.next)
                .focused($focusedField, equals: .email)
                .onSubmit {
                    focusedField = .password
                }

            SecureField(&amp;quot;Password&amp;quot;, text: $password)
                .textContentType(.password)
                .submitLabel(.done)
                .focused($focusedField, equals: .password)
                .onSubmit {
                    focusedField = nil
                }

            Button(&amp;quot;Sign in&amp;quot;) {
                validate()
            }
        }
    }

    private func validate() {
        if email.isEmpty {
            focusedField = .email
        } else if password.isEmpty {
            focusedField = .password
        } else {
            focusedField = nil
            signIn()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each field is connected to its corresponding enum case using &lt;code&gt;focused(_:equals:)&lt;/code&gt;. When a field receives focus, &lt;code&gt;focusedField&lt;/code&gt; is updated with that case. Assigning a case programmatically moves focus to the associated field.&lt;/p&gt;&lt;p&gt;The value is optional because focus can also be outside both fields. Setting it to &lt;code&gt;nil&lt;/code&gt; removes focus from all views connected to this property and dismisses the keyboard.&lt;/p&gt;&lt;h3&gt;Avoiding ambiguous focus bindings&lt;/h3&gt;&lt;p&gt;Each focus value should identify one focus destination. Connecting the same enum case to multiple views makes programmatic focus ambiguous:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;TextField(&amp;quot;First name&amp;quot;, text: $firstName)
    .focused($focusedField, equals: .name)

TextField(&amp;quot;Last name&amp;quot;, text: $lastName)
    .focused($focusedField, equals: .name)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When either field receives focus, &lt;code&gt;focusedField&lt;/code&gt; becomes &lt;code&gt;.name&lt;/code&gt;. But when we assign &lt;code&gt;.name&lt;/code&gt; ourselves, SwiftUI cannot determine which field should receive focus. Using a separate enum case for each destination avoids this ambiguity.&lt;/p&gt;&lt;h3&gt;Passing focus state to a child view&lt;/h3&gt;&lt;p&gt;Sometimes the focusable control is extracted into a child view. In this case, we can pass the projected focus state as a &lt;code&gt;FocusState.Binding&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ParentView: View {
    @FocusState private var isNameFocused: Bool

    var body: some View {
        NameField(isFocused: $isNameFocused)
    }
}

struct NameField: View {
    @State private var name = &amp;quot;&amp;quot;
    @FocusState.Binding var isFocused: Bool

    var body: some View {
        TextField(&amp;quot;Name&amp;quot;, text: $name)
            .focused($isFocused)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is useful when the child contains the view whose focus is represented by the property. The child attaches the binding to that view with the &lt;code&gt;focused(_:)&lt;/code&gt; modifier, while the parent can still observe and change the focus.&lt;/p&gt;</content:encoded><category>property wrappers</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Building a reusable API client with URLSession in Swift]]></title><description><![CDATA[Learn how to structure networking code with URLSession, async/await, response validation, and JSON decoding.]]></description><link>https://tanaschita.com/swift-urlsession</link><guid isPermaLink="false">https://tanaschita.com/swift-urlsession</guid><pubDate>Sat, 01 Aug 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;URLSession&lt;/code&gt; is the foundation framework for loading data over the network in Swift. We can use it directly, but networking code can quickly become repetitive.&lt;/p&gt;&lt;p&gt;Most endpoints need the same steps: create a request, call &lt;code&gt;URLSession&lt;/code&gt;, validate the HTTP response, decode JSON, and handle errors. Instead of repeating this flow for every endpoint, we can move the shared behavior into a small reusable API client.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll build a lightweight &lt;code&gt;APIClient&lt;/code&gt; with &lt;code&gt;URLSession&lt;/code&gt; and async/await.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Starting with a request&lt;/h3&gt;&lt;p&gt;For very simple &lt;code&gt;GET&lt;/code&gt; requests, &lt;code&gt;URLSession&lt;/code&gt; provides &lt;code&gt;data(from:)&lt;/code&gt;, which only needs a URL. In most apps, &lt;code&gt;data(for:)&lt;/code&gt; is more useful because it accepts a &lt;code&gt;URLRequest&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;With a &lt;code&gt;URLRequest&lt;/code&gt;, we can configure details like the HTTP method, headers, timeout, cache policy, or request body:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let url = URL(string: &amp;quot;https://example.com/api/articles&amp;quot;)!

var request = URLRequest(url: url)
request.httpMethod = &amp;quot;GET&amp;quot;
request.setValue(&amp;quot;application/json&amp;quot;, forHTTPHeaderField: &amp;quot;Accept&amp;quot;)

let (data, response) = try await URLSession.shared.data(for: request)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The method returns a tuple containing the response body as &lt;code&gt;Data&lt;/code&gt; and a &lt;code&gt;URLResponse&lt;/code&gt;. Calling &lt;code&gt;data(for:)&lt;/code&gt; also creates and resumes a data task internally, so we don&amp;#x27;t need to call &lt;code&gt;resume()&lt;/code&gt; ourselves.&lt;/p&gt;&lt;p&gt;This already works, but the same pattern usually appears in many places. Let&amp;#x27;s move it into a reusable client.&lt;/p&gt;&lt;h3&gt;Creating an API client&lt;/h3&gt;&lt;p&gt;The first version of the client can take a &lt;code&gt;URLRequest&lt;/code&gt;, load the data, validate the response, and decode the result.&lt;/p&gt;&lt;p&gt;First, let&amp;#x27;s define a small error type for response validation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum NetworkError: Error {
    case invalidResponse
    case invalidStatusCode(Int)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we can define a lightweight endpoint type. It describes what is specific to one request, while the client owns shared configuration like the base URL and default headers.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum HTTPMethod: String {
    case get = &amp;quot;GET&amp;quot;
    case post = &amp;quot;POST&amp;quot;
}

struct Endpoint {
    let path: String
    let method: HTTPMethod
    var headers: [String: String] = [:]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can use it inside the client:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct APIClient {
    let session: URLSession
    let decoder: JSONDecoder
    let baseURL: URL
    let defaultHeaders: [String: String]

    init(
        baseURL: URL,
        session: URLSession = .shared,
        decoder: JSONDecoder = JSONDecoder(),
        defaultHeaders: [String: String] = [&amp;quot;Accept&amp;quot;: &amp;quot;application/json&amp;quot;]
    ) {
        self.baseURL = baseURL
        self.session = session
        self.decoder = decoder
        self.defaultHeaders = defaultHeaders
    }

    func send&amp;lt;Value: Decodable&amp;gt;(
        _ request: URLRequest,
        decode type: Value.Type = Value.self
    ) async throws -&amp;gt; Value {
        let (data, response) = try await session.data(for: request)
        try validate(response)

        return try decoder.decode(Value.self, from: data)
    }

    private func validate(_ response: URLResponse) throws {
        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.invalidResponse
        }

        guard (200...299).contains(httpResponse.statusCode) else {
            throw NetworkError.invalidStatusCode(httpResponse.statusCode)
        }
    }

    func request(for endpoint: Endpoint) -&amp;gt; URLRequest {
        let url = baseURL.appending(path: endpoint.path)

        var request = URLRequest(url: url)
        request.httpMethod = endpoint.method.rawValue

        let headers = defaultHeaders.merging(endpoint.headers) { _, endpointValue in
            endpointValue
        }

        for (field, value) in headers {
            request.setValue(value, forHTTPHeaderField: field)
        }

        return request
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gives the app one place for the repeated networking behavior. The client knows the base URL, applies shared headers, validates responses, and decodes the received data.&lt;/p&gt;&lt;h3&gt;Why response validation matters&lt;/h3&gt;&lt;p&gt;&lt;code&gt;URLSession&lt;/code&gt; throws for transport-level errors, such as a failed connection or cancellation. An HTTP error status code is different. A &lt;code&gt;404&lt;/code&gt; or &lt;code&gt;500&lt;/code&gt; response is still a server response, so it will not automatically throw just because of the status code. This is why the client checks the &lt;code&gt;HTTPURLResponse&lt;/code&gt; before decoding.&lt;/p&gt;&lt;h3&gt;Loading decoded models&lt;/h3&gt;&lt;p&gt;Now the client can return decoded models instead of raw &lt;code&gt;Data&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;For example, we might have an &lt;code&gt;Article&lt;/code&gt; type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Article: Decodable {
    let id: Int
    let title: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then an endpoint-specific function can create the request and ask the client to send it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func loadArticles(client: APIClient) async throws -&amp;gt; [Article] {
    let endpoint = Endpoint(path: &amp;quot;/api/articles&amp;quot;, method: .get)
    let request = client.request(for: endpoint)

    return try await client.send(request, decode: [Article].self)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the call site, we can use the function from another async context:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;do {
    let articles = try await loadArticles()
    // Update state with articles.
} catch {
    // Show an error state.
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Adding endpoint-specific headers&lt;/h3&gt;&lt;p&gt;The default headers are applied by the client. If an endpoint needs additional headers, or needs to override a default value, it can still provide its own headers.&lt;/p&gt;&lt;p&gt;Now loading articles becomes a little more focused:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let client = APIClient(baseURL: URL(string: &amp;quot;https://example.com&amp;quot;)!)

let endpoint = Endpoint(
    path: &amp;quot;/api/articles&amp;quot;,
    method: .get
)

let request = client.request(for: endpoint)
let articles = try await client.send(request, decode: [Article].self)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And if one endpoint needs a custom header, we can keep that local to the endpoint:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let endpoint = Endpoint(
    path: &amp;quot;/api/profile&amp;quot;,
    method: .get,
    headers: [&amp;quot;Authorization&amp;quot;: &amp;quot;Bearer token&amp;quot;]
)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Configuring URLSession&lt;/h3&gt;&lt;p&gt;If we need more control, we can use a configured session instead of &lt;code&gt;URLSession.shared&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
configuration.requestCachePolicy = .reloadRevalidatingCacheData

let session = URLSession(configuration: configuration)
let client = APIClient(
    baseURL: URL(string: &amp;quot;https://example.com&amp;quot;)!,
    session: session
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the client receives the session in its initializer, we can also pass a custom session in tests.&lt;/p&gt;&lt;h3&gt;Cancellation&lt;/h3&gt;&lt;p&gt;The async &lt;code&gt;URLSession&lt;/code&gt; methods work naturally with Swift concurrency cancellation. If the task waiting for the request is cancelled, the underlying network request can be cancelled too.&lt;/p&gt;&lt;p&gt;For example, SwiftUI cancels work started with the &lt;code&gt;task&lt;/code&gt; modifier when the view disappears:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ArticlesView: View {
    @State private var articles: [Article] = []

    var body: some View {
        List(articles, id: \.id) { article in
            Text(article.title)
        }
        .task {
            do {
                articles = try await loadArticles()
            } catch is CancellationError {
                // The request was cancelled because the result is no longer needed.
            } catch {
                // Show an error state.
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cancellation is not necessarily an error the user needs to see. In many cases, it simply means that the result is no longer needed, for example because the view disappeared or a newer request replaced the old one.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;&lt;code&gt;URLSession&lt;/code&gt; gives us the low-level building blocks for networking, but a small API client can make the app code easier to maintain. By moving request sending, response validation, and JSON decoding into one place, each feature can focus on the endpoint it needs.&lt;/p&gt;</content:encoded><category>async/await</category><category>networking</category><category>swift</category></item><item><title><![CDATA[Understanding code signing and provisioning in iOS]]></title><description><![CDATA[Learn how certificates, App IDs, entitlements, and provisioning profiles work together.]]></description><link>https://tanaschita.com/ios-code-signing-provisioning</link><guid isPermaLink="false">https://tanaschita.com/ios-code-signing-provisioning</guid><pubDate>Thu, 23 Jul 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building an iOS app, we eventually need to run it on a real device, send it to testers, or distribute it through the App Store.&lt;/p&gt;&lt;p&gt;For that to work, the app has to be signed. Code signing tells the system who created the app and helps ensure that the app has not been changed after it was signed. Provisioning tells the system what the app is allowed to do and where it is allowed to run.&lt;/p&gt;&lt;p&gt;Xcode can manage most of this automatically, but it helps to understand what happens behind the scenes, especially when working in larger teams, CI environments, or projects with stricter release processes.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The mental model&lt;/h3&gt;&lt;p&gt;The code signing setup can be divided into three main parts:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App target
- bundle identifier
- entitlements

Certificate
- signing identity
- private key

Provisioning profile
- App ID
- allowed certificates
- allowed entitlements
- distribution type
- devices, if needed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The app target defines what is being signed. The certificate identifies who signs it. The provisioning profile connects both sides by saying which App ID can be signed with which certificates, which entitlements are allowed, and where the app can run.&lt;/p&gt;&lt;p&gt;Signing works when these pieces match. If one of them is out of sync, Xcode cannot sign the app or iOS refuses to install it.&lt;/p&gt;&lt;h3&gt;Certificates&lt;/h3&gt;&lt;p&gt;A certificate identifies who is signing the app. It is issued by Apple and is connected to a private key stored in the keychain or managed by the signing system.&lt;/p&gt;&lt;p&gt;For iOS development, there are two common certificate categories:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;development certificates for running and debugging apps on devices&lt;/li&gt;&lt;li&gt;distribution certificates for TestFlight, App Store, Ad Hoc, or other distribution workflows&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When signing locally, the certificate alone is not enough. The machine also needs the matching private key. This is why copying only a certificate file to another Mac often does not fix signing problems. The private key has to be available too.&lt;/p&gt;&lt;p&gt;For many distribution workflows, Xcode can also use cloud-managed certificates. In that case, Apple manages the distribution certificate remotely and Xcode can sign through the Organizer distribution flow without requiring a local distribution certificate in the keychain.&lt;/p&gt;&lt;h3&gt;App IDs&lt;/h3&gt;&lt;p&gt;An App ID identifies the app in the Apple Developer account. It is connected to the app&amp;#x27;s bundle identifier, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;com.example.MyApp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The bundle identifier in Xcode has to match the App ID used for signing. If the app has extensions, each extension has its own bundle identifier and usually its own App ID as well.&lt;/p&gt;&lt;p&gt;The App ID is also where capabilities are enabled in the developer account. For example, if the app uses Push Notifications, Associated Domains, App Groups, iCloud, or Sign in with Apple, the App ID needs to support those capabilities.&lt;/p&gt;&lt;h3&gt;Entitlements&lt;/h3&gt;&lt;p&gt;Entitlements are values embedded into the signed app. They describe which protected capabilities the app wants to use.&lt;/p&gt;&lt;p&gt;In Xcode, we usually configure them in the &lt;code&gt;Signing &amp;amp; Capabilities&lt;/code&gt; tab. Xcode then stores them in an &lt;code&gt;.entitlements&lt;/code&gt; file and includes them when signing the app.&lt;/p&gt;&lt;p&gt;For example, an app using Associated Domains might include an entitlement like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;com.apple.developer.associated-domains&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;string&amp;gt;applinks:example.com&amp;lt;/string&amp;gt;
&amp;lt;/array&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The important detail is that entitlements have to match the provisioning profile. If an entitlement is present in the app but not allowed by the profile, signing or installation can fail.&lt;/p&gt;&lt;h3&gt;Provisioning profiles&lt;/h3&gt;&lt;p&gt;A provisioning profile is the file that ties everything together. It says:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;which App ID the app uses&lt;/li&gt;&lt;li&gt;which certificates are allowed to sign it&lt;/li&gt;&lt;li&gt;which entitlements are allowed&lt;/li&gt;&lt;li&gt;which distribution method it is for&lt;/li&gt;&lt;li&gt;which devices it can run on, if the profile is device-based&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Provisioning profiles use the &lt;code&gt;.mobileprovision&lt;/code&gt; file extension for iOS apps.&lt;/p&gt;&lt;p&gt;There are different profile types depending on what we want to do:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;development profiles are used for running and debugging on devices&lt;/li&gt;&lt;li&gt;Ad Hoc profiles are used for distributing to registered devices&lt;/li&gt;&lt;li&gt;App Store profiles are used for App Store and TestFlight distribution&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Development and Ad Hoc profiles include devices. App Store profiles do not, because App Store distribution is not limited to specific registered test devices.&lt;/p&gt;&lt;h3&gt;Automatic signing&lt;/h3&gt;&lt;p&gt;With automatic signing, Xcode creates and updates signing assets for us. We select a team in the &lt;code&gt;Signing &amp;amp; Capabilities&lt;/code&gt; tab, enable &lt;code&gt;Automatically manage signing&lt;/code&gt;, and Xcode handles the required App IDs, certificates, and provisioning profiles when possible.&lt;/p&gt;&lt;p&gt;This is usually the most convenient setup for local development. It is also a good default when the project is not doing anything unusual.&lt;/p&gt;&lt;p&gt;Automatic signing becomes especially helpful when adding capabilities. If we add a capability in Xcode, Xcode can update the App ID and provisioning profile so the app&amp;#x27;s entitlements continue to match the profile.&lt;/p&gt;&lt;h3&gt;Manual signing&lt;/h3&gt;&lt;p&gt;With manual signing, we create and select the signing assets ourselves. This means choosing the provisioning profile and certificate for each relevant target and build configuration.&lt;/p&gt;&lt;p&gt;Manual signing can be useful when:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;a CI system needs predictable signing assets&lt;/li&gt;&lt;li&gt;a team wants tighter control over profiles and certificates&lt;/li&gt;&lt;li&gt;different configurations use different bundle identifiers&lt;/li&gt;&lt;li&gt;the app has multiple targets or extensions with separate provisioning needs&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Manual signing also means we are responsible for keeping profiles up to date. If we add a capability, register a new device, or change the App ID, the provisioning profile may need to be regenerated and downloaded again.&lt;/p&gt;&lt;h3&gt;How Xcode signs the app&lt;/h3&gt;&lt;p&gt;When Xcode signs an app, it combines the build product with the selected signing identity and provisioning profile.&lt;/p&gt;&lt;p&gt;For the signing step to succeed, the important values have to match:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the app&amp;#x27;s bundle identifier has to match the App ID in the provisioning profile&lt;/li&gt;&lt;li&gt;the certificate has to be included in the provisioning profile&lt;/li&gt;&lt;li&gt;the app&amp;#x27;s entitlements have to be allowed by the provisioning profile&lt;/li&gt;&lt;li&gt;the profile type has to match the build or distribution workflow&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If the app contains extensions, the same applies to each extension target. The app target and every extension target need their own matching signing setup.&lt;/p&gt;&lt;h3&gt;Common signing problems&lt;/h3&gt;&lt;p&gt;Many signing errors come from one of a few mismatches.&lt;/p&gt;&lt;p&gt;If Xcode says that a provisioning profile does not include a signing certificate, the selected certificate is not part of that profile. This can happen after regenerating certificates or switching machines.&lt;/p&gt;&lt;p&gt;If Xcode says that a capability is not supported by the provisioning profile, the App ID or profile does not include the entitlement used by the app. Regenerating the profile after enabling the capability usually fixes this.&lt;/p&gt;&lt;p&gt;If an app installs on one device but not another, the development or Ad Hoc profile may not include the second device. In that case, the device needs to be registered and the profile needs to be updated.&lt;/p&gt;&lt;p&gt;If CI fails while local builds work, the CI environment may be missing the private key, the provisioning profile, or access to automatic signing. In that case, either the signing assets need to be installed in CI, or the CI workflow needs permission to manage signing through Xcode or App Store Connect.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;Code signing and provisioning are easier to reason about when we separate the responsibilities.&lt;/p&gt;&lt;p&gt;A certificate identifies who signs the app. An App ID identifies the app. Entitlements describe what the app wants to do. A provisioning profile connects those pieces and defines where the signed app can run.&lt;/p&gt;&lt;p&gt;Automatic signing lets Xcode manage most of this for us. Manual signing gives us more control, but also makes us responsible for keeping certificates, profiles, entitlements, bundle identifiers, and devices in sync.&lt;/p&gt;</content:encoded><category>app distribution</category><category>continuous integration</category><category>ios</category></item><item><title><![CDATA[Building widgets with WidgetKit and SwiftUI]]></title><description><![CDATA[Learn how to create widgets for an iOS application with WidgetKit and SwiftUI.]]></description><link>https://tanaschita.com/widgetkit-building-widgets-with-swiftui</link><guid isPermaLink="false">https://tanaschita.com/widgetkit-building-widgets-with-swiftui</guid><pubDate>Sun, 19 Jul 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Widgets give users a lightweight way to see useful information from an app without opening it first. A good widget is usually focused on one small job: showing today&amp;#x27;s progress, the next event, the current status, or a shortcut into a specific part of the app.&lt;/p&gt;&lt;p&gt;WidgetKit handles the system integration, scheduling, rendering, and configuration. We provide the data, decide when it should refresh, and build the view with SwiftUI.&lt;/p&gt;&lt;p&gt;Before building a widget, it helps to decide what information is useful at a glance. Widgets work best when they show a small amount of timely content or provide a shortcut to a focused action. If the user needs to make several decisions, the app itself is usually the better place for that flow.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to add a widget extension, configure a widget, provide timeline entries, and build the widget&amp;#x27;s SwiftUI view.&lt;/p&gt;&lt;p&gt;We&amp;#x27;ll use a simple quote widget as an example. The same structure can be applied to other types of widgets, such as a task summary, a weather overview, or a project status widget.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding a widget extension&lt;/h3&gt;&lt;p&gt;A widget lives in its own app extension. To add one to an existing iOS app, we can use Xcode&amp;#x27;s &lt;code&gt;Widget Extension&lt;/code&gt; template from &lt;code&gt;File &amp;gt; New &amp;gt; Target&lt;/code&gt; and enter a name for the extension.&lt;/p&gt;&lt;p&gt;Xcode creates a new target with a widget entry point. The main widget type conforms to the &lt;code&gt;Widget&lt;/code&gt; protocol and is marked with &lt;code&gt;@main&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import WidgetKit

@main
struct ExampleWidget: Widget {
    var body: some WidgetConfiguration {
        // Return the widget configuration.
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The configuration describes what kind of widget we are building, which timeline provider supplies its data, and which SwiftUI view renders the widget.&lt;/p&gt;&lt;h3&gt;Choosing a widget configuration&lt;/h3&gt;&lt;h4&gt;Static widgets&lt;/h4&gt;&lt;p&gt;For many widgets, &lt;code&gt;StaticConfiguration&lt;/code&gt; is enough. It works well when the widget does not need user-configurable options.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some WidgetConfiguration {
    StaticConfiguration(
        kind: &amp;quot;com.example.QuoteWidget&amp;quot;,
        provider: ExampleTimelineProvider()
    ) { entry in
        QuoteWidgetView(entry: entry)
    }
    .configurationDisplayName(&amp;quot;Quote Widget&amp;quot;)
    .description(&amp;quot;Shows the latest quote of the day.&amp;quot;)
    .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;kind&lt;/code&gt; identifies the widget, the provider supplies its timeline entries, and the closure builds the SwiftUI view. The remaining modifiers define how the widget appears when users add or edit it.&lt;/p&gt;&lt;h4&gt;Configurable widgets&lt;/h4&gt;&lt;p&gt;If the widget should be configurable by the user, we can use &lt;code&gt;AppIntentConfiguration&lt;/code&gt; instead. This is useful when the widget content depends on a selected project, category, account, or any other user-provided option.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some WidgetConfiguration {
    AppIntentConfiguration(
        kind: &amp;quot;com.example.QuoteWidget&amp;quot;,
        intent: QuoteWidgetIntent.self,
        provider: QuoteTimelineProvider()
    ) { entry in
        QuoteWidgetView(entry: entry)
    }
    .configurationDisplayName(&amp;quot;Quote&amp;quot;)
    .description(&amp;quot;Shows a quote for the selected category.&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The configuration looks similar to the static one, but it also receives an intent type. The intent describes the values users can configure:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import AppIntents
import WidgetKit

enum QuoteCategory: String, AppEnum {
    case motivation
    case focus

    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: &amp;quot;Category&amp;quot;)

    static var caseDisplayRepresentations: [QuoteCategory: DisplayRepresentation] = [
        .motivation: &amp;quot;Motivation&amp;quot;,
        .focus: &amp;quot;Focus&amp;quot;
    ]
}

struct QuoteWidgetIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = &amp;quot;Quote Widget&amp;quot;

    @Parameter(title: &amp;quot;Category&amp;quot;)
    var category: QuoteCategory = .motivation
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the user can choose between predefined quote categories when adding or editing the widget. The selected category is passed to the provider as part of the &lt;code&gt;QuoteWidgetIntent&lt;/code&gt; configuration, so the provider can create entries for the chosen category.&lt;/p&gt;&lt;p&gt;For a more detailed walkthrough, check out the article on &lt;a href=&quot;/widgetkit-configurable-widgets-swiftui&quot;&gt;how to build a configurable widget with WidgetKit and SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Providing timeline entries&lt;/h3&gt;&lt;p&gt;Widgets don&amp;#x27;t run continuously in the background. Instead, WidgetKit asks the timeline provider for entries and decides when to render them.&lt;/p&gt;&lt;p&gt;A timeline entry is a small model object that conforms to &lt;code&gt;TimelineEntry&lt;/code&gt;. It must contain a &lt;code&gt;date&lt;/code&gt; and can include any other values the widget view needs:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteEntry: TimelineEntry {
    let date: Date
    let quote: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a static widget, the provider conforms to &lt;code&gt;TimelineProvider&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ExampleTimelineProvider: TimelineProvider {
    func placeholder(in context: Context) -&amp;gt; QuoteEntry {
        QuoteEntry(date: Date(), quote: &amp;quot;Quote of the day&amp;quot;)
    }

    func getSnapshot(in context: Context, completion: @escaping (QuoteEntry) -&amp;gt; Void) {
        let entry = QuoteEntry(date: Date(), quote: &amp;quot;Quote of the day&amp;quot;)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline&amp;lt;QuoteEntry&amp;gt;) -&amp;gt; Void) {
        let entry = QuoteEntry(date: Date(), quote: &amp;quot;Small steps count.&amp;quot;)
        let nextUpdate = Date().addingTimeInterval(60 * 60)
        let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))

        completion(timeline)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s look at the methods more closely:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;placeholder(in:)&lt;/code&gt; provides a lightweight representation of the widget before real data is available.&lt;/li&gt;&lt;li&gt;&lt;code&gt;getSnapshot(in:completion:)&lt;/code&gt; provides a single entry for previews and transient states.&lt;/li&gt;&lt;li&gt;&lt;code&gt;getTimeline(in:completion:)&lt;/code&gt; provides one or more entries and a reload policy.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In practice, &lt;code&gt;placeholder&lt;/code&gt; should be generic, while &lt;code&gt;snapshot&lt;/code&gt; can use realistic sample or current data.&lt;/p&gt;&lt;p&gt;The reload policy is only a request. WidgetKit decides the exact refresh timing based on system conditions, so a widget should not depend on second-by-second updates.&lt;/p&gt;&lt;p&gt;For a configurable widget, the idea is the same, but the provider conforms to &lt;code&gt;AppIntentTimelineProvider&lt;/code&gt;. Its &lt;code&gt;snapshot&lt;/code&gt; and &lt;code&gt;timeline&lt;/code&gt; methods receive the selected configuration, which lets us use values like &lt;code&gt;configuration.category&lt;/code&gt; when creating the entries.&lt;/p&gt;&lt;h3&gt;Building the widget view with SwiftUI&lt;/h3&gt;&lt;p&gt;The widget view is a regular SwiftUI view, but it should stay focused and lightweight. It receives a timeline entry and renders the current state:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteWidgetView: View {
    let entry: QuoteEntry

    var body: some View {
        Text(entry.quote)
            .font(.headline)
            .containerBackground(.background, for: .widget)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Widgets can appear in different sizes and contexts. We can use the &lt;code&gt;widgetFamily&lt;/code&gt; environment value to adapt the layout:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteWidgetView: View {
    @Environment(\.widgetFamily) private var family

    let entry: QuoteEntry

    var body: some View {
        switch family {
        case .systemSmall:
            Text(entry.quote)
                .font(.headline)

        case .systemMedium:
            VStack(alignment: .leading) {
                Text(&amp;quot;Quote&amp;quot;)
                    .font(.caption)
                Text(entry.quote)
                    .font(.headline)
            }

        default:
            Text(entry.quote)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When possible, keep the view resilient. The widget might render with placeholder data, stale data, or in a smaller family than expected.&lt;/p&gt;&lt;p&gt;Because the widget view is built with SwiftUI, we can preview different entries and widget families in Xcode while adjusting the layout.&lt;/p&gt;&lt;h3&gt;Opening the app from a widget&lt;/h3&gt;&lt;p&gt;The simplest interaction is opening the app when the user taps the widget. To open a specific screen, the widget needs a URL that the app can handle. This can be a custom URL scheme like &lt;code&gt;myapp://quote/today&lt;/code&gt;, or a universal link if the same destination should also open from outside the app.&lt;/p&gt;&lt;p&gt;For a single destination, we can attach a URL to a view in the widget hierarchy:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(entry.quote)
    .widgetURL(URL(string: &amp;quot;myapp://quote/today&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In SwiftUI, the app can handle the URL with &lt;code&gt;onOpenURL(perform:)&lt;/code&gt; and route the user to the matching screen:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct ExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    // Route to the matching screen.
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For widgets that show multiple tappable areas, we can use &lt;code&gt;Link&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Link(destination: URL(string: &amp;quot;myapp://quote/today&amp;quot;)!) {
    Text(entry.quote)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Adding interactive controls&lt;/h3&gt;&lt;p&gt;On newer OS versions, widgets can also support simple interactions without launching the app. For example, a button can run an &lt;code&gt;AppIntent&lt;/code&gt; to update shared app data.&lt;/p&gt;&lt;p&gt;Interactive widgets are best suited for small, predictable actions. If an action requires a longer flow, authentication, or more context, opening the app is usually the better experience.&lt;/p&gt;&lt;h3&gt;Sharing data with the widget extension&lt;/h3&gt;&lt;p&gt;Because the widget runs in a separate extension, it needs access to the data it displays. For simple values, this can be done with shared &lt;code&gt;UserDefaults&lt;/code&gt; in an App Group. For larger or structured data, we can use a shared container or another persistence setup that both the app and widget extension can access.&lt;/p&gt;&lt;h3&gt;Reloading widget timelines&lt;/h3&gt;&lt;p&gt;When app data changes, we can ask WidgetKit to reload the widget&amp;#x27;s timeline:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;WidgetCenter.shared.reloadTimelines(ofKind: &amp;quot;com.example.QuoteWidget&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To reload all timelines for the app&amp;#x27;s widgets, we can call:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;WidgetCenter.shared.reloadAllTimelines()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is useful after saving data that is displayed in the widget. Still, the system remains in control of the final refresh timing, so the widget should always display a reasonable state until the next update happens.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;To build a WidgetKit widget, we create a widget extension, choose a configuration, provide timeline entries, and render the current entry with SwiftUI.&lt;/p&gt;&lt;p&gt;For simple widgets, &lt;code&gt;StaticConfiguration&lt;/code&gt; and &lt;code&gt;TimelineProvider&lt;/code&gt; are enough. For user-configurable widgets, &lt;code&gt;AppIntentConfiguration&lt;/code&gt; and &lt;code&gt;AppIntentTimelineProvider&lt;/code&gt; provide a modern way to model the configuration. From there, we can decide whether tapping the widget should open the app, or whether a small action can be handled directly with an &lt;code&gt;AppIntent&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>widgetkit</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Keeping SwiftData behind a boundary]]></title><description><![CDATA[Learn how to keep SwiftData out of SwiftUI views.]]></description><link>https://tanaschita.com/swiftdata-persistence-boundaries</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-persistence-boundaries</guid><pubDate>Sun, 12 Jul 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftData integrates naturally with SwiftUI. It lets us build persistence into an app quickly, but it also makes it easy to mix persistence details directly into views.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how we can create a boundary between the UI and the persistence layer when working with SwiftData.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;A view can read from a &lt;code&gt;ModelContext&lt;/code&gt;, use &lt;code&gt;@Query&lt;/code&gt;, create model objects, insert them, delete them, and save the context. For a small prototype this can feel very convenient. In a real app, however, this kind of direct access can become one of the first places where responsibilities start to blur.&lt;/p&gt;&lt;p&gt;The direct version of a SwiftData-backed view often starts like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ItemListView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [PersistedItem]

    var body: some View {
        List(items) { item in
            Text(item.timestamp, format: .dateTime)
        }
        .toolbar {
            Button(&amp;quot;Add&amp;quot;) {
                modelContext.insert(PersistedItem(timestamp: Date()))
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is compact, and SwiftData makes the persistence work almost invisible. The tradeoff is that the view now knows quite a lot about how data is stored. If we later add validation, error handling, sorting rules, sync behavior, test data, or a different persistence strategy, the view might take on too many responsibilities.&lt;/p&gt;&lt;p&gt;To keep responsibilities clean, we can put a small persistence boundary between the UI-facing code and the storage. We can make the persisted type explicit:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Model
final class PersistedItem {
    var id: UUID
    var timestamp: Date

    init(id: UUID = UUID(), timestamp: Date) {
        self.id = id
        self.timestamp = timestamp
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The UI-facing model can then be a plain value type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Item: Identifiable, Equatable, Sendable {
    let id: UUID
    let timestamp: Date
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;PersistedItem&lt;/code&gt; tells us that this type belongs to the storage layer. &lt;code&gt;Item&lt;/code&gt; is the value the feature works with. This makes the boundary visible in the code and reduces the chance that a persistence model slowly spreads through views, navigation state, previews, and tests.&lt;/p&gt;&lt;p&gt;The repository protocol can describe the feature&amp;#x27;s persistence needs without exposing SwiftData:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@MainActor
protocol ItemRepository {
    func fetchItems() throws -&amp;gt; [Item]
    func addItem(timestamp: Date) throws
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The view model depends on this boundary instead of depending on SwiftData directly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@MainActor
@Observable
final class ItemListViewModel {
    private let repository: any ItemRepository
    private(set) var items: [Item] = []

    init(repository: any ItemRepository) {
        self.repository = repository
    }

    func loadItems() throws {
        items = try repository.fetchItems()
    }

    func addItem() throws {
        try repository.addItem(timestamp: Date())
        try loadItems()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the view can stay focused on presentation and user interaction. It displays &lt;code&gt;viewModel.items&lt;/code&gt; and calls methods such as &lt;code&gt;viewModel.loadItems()&lt;/code&gt; or &lt;code&gt;viewModel.addItem()&lt;/code&gt;, but it no longer knows about &lt;code&gt;ModelContext&lt;/code&gt;, fetch descriptors, or SwiftData insert calls.&lt;/p&gt;&lt;p&gt;The SwiftData-specific implementation moves into the repository:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@MainActor
final class ItemRepositoryImpl: ItemRepository {
    private let modelContext: ModelContext

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    func fetchItems() throws -&amp;gt; [Item] {
        let descriptor = FetchDescriptor&amp;lt;PersistedItem&amp;gt;(
            sortBy: [SortDescriptor(\.timestamp, order: .forward)]
        )
        return try modelContext.fetch(descriptor).map { persistedItem in
            Item(id: persistedItem.id, timestamp: persistedItem.timestamp)
        }
    }

    func addItem(timestamp: Date) throws {
        modelContext.insert(PersistedItem(timestamp: timestamp))
        try modelContext.save()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This stronger boundary comes with a tradeoff: we now have two types and mapping code between them. For very small prototypes, that can feel like extra work. In an app that is meant to grow, however, the advantages are often worth it:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;PersistedItem&lt;/code&gt; makes it clear which type belongs to SwiftData.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Item&lt;/code&gt; can be a simple value type that is easier to pass around, compare, preview, and test.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Item&lt;/code&gt; can conform to &lt;code&gt;Sendable&lt;/code&gt;, which fits better with Swift concurrency than passing persistence-managed reference objects through the app.&lt;/li&gt;&lt;li&gt;Migration concerns stay closer to the persistence layer.&lt;/li&gt;&lt;li&gt;The repository becomes the place where storage details, mapping, fetch descriptors, inserts, and saves are handled.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With a boundary in place, the view model can be tested with an in-memory or fake repository, previews can provide controlled data, and the persistence implementation can evolve in a controlled way.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>architecture</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding Sendable in Swift]]></title><description><![CDATA[Learn what Sendable means in Swift concurrency and how it helps prevent data races.]]></description><link>https://tanaschita.com/swift-concurrency-sendable</link><guid isPermaLink="false">https://tanaschita.com/swift-concurrency-sendable</guid><pubDate>Sun, 05 Jul 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with Swift concurrency, we often pass values between tasks, actors, and async functions.&lt;/p&gt;&lt;p&gt;We may fetch data in one task, send it to another part of the app, update the UI, or ask an actor to store something for us. The part to watch out for here is that concurrent code can run at the same time.&lt;/p&gt;&lt;p&gt;If two parts of the program can access and mutate the same value at once, we can end up with a data race. &lt;code&gt;Sendable&lt;/code&gt; helps Swift check whether values are safe to pass across concurrency boundaries, such as into a task or actor.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at what &lt;code&gt;Sendable&lt;/code&gt; means in more detail, when Swift checks it, how to make our own types conform to it, and what to do when the compiler complains.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is Sendable?&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Sendable&lt;/code&gt; is a protocol that marks a type as safe to pass across concurrency boundaries.&lt;/p&gt;&lt;p&gt;A concurrency boundary is a place where data moves from one isolated context to another. For example, when we call a method on an actor, Swift may need to move values between the caller&amp;#x27;s context and the actor&amp;#x27;s isolated context:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor UserStore {
    private var users: [User] = []

    func save(_ user: User) {
        users.append(user)
    }
}

let store = UserStore()
await store.save(user)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;user&lt;/code&gt; moves from the caller&amp;#x27;s isolation context into &lt;code&gt;UserStore&lt;/code&gt;’s actor isolation. That is a concurrency boundary.&lt;/p&gt;&lt;p&gt;The compiler will check whether this value can be safely transferred. How strictly it does so depends on the Swift version and concurrency settings. In Swift 6 with strict concurrency enabled, &lt;code&gt;Sendable&lt;/code&gt; violations are errors. In Swift 5 with the default settings, many of them are warnings.&lt;/p&gt;&lt;p&gt;For value types like structs and enums, this usually works without any extra code because Swift can infer sendability when all stored properties are sendable:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct User {
    let id: UUID
    let name: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Like many standard Swift types, &lt;code&gt;UUID&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; are sendable, and &lt;code&gt;User&lt;/code&gt; does not contain shared mutable reference state.&lt;/p&gt;&lt;p&gt;We can also make this promise explicit by conforming to &lt;code&gt;Sendable&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct User: Sendable {
    let id: UUID
    let name: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Adding &lt;code&gt;Sendable&lt;/code&gt; documents the intent of the type and lets the compiler help us keep it safe over time. For example, if someone later adds a mutable reference-type property to &lt;code&gt;User&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct User: Sendable {
    let id: UUID
    let name: String
    var session: Session // ❌ compiler error if Session is not Sendable
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If &lt;code&gt;User&lt;/code&gt; itself becomes a mutable reference type, we also get a compiler error that says something like: &lt;code&gt;Sending self.user risks causing data races&lt;/code&gt;. Let&amp;#x27;s look at how to handle reference types.&lt;/p&gt;&lt;h3&gt;Sendable with classes&lt;/h3&gt;&lt;p&gt;Classes need more care because they are reference types. When we pass a class instance around, we are passing a reference to the same object.&lt;/p&gt;&lt;p&gt;This can be safe if the class is immutable:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class AppConfiguration: Sendable {
    let apiBaseURL: URL
    let featureFlags: [String: Bool]

    init(apiBaseURL: URL, featureFlags: [String: Bool]) {
        self.apiBaseURL = apiBaseURL
        self.featureFlags = featureFlags
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This class is &lt;code&gt;final&lt;/code&gt;, all stored properties are immutable, and all stored properties are &lt;code&gt;Sendable&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;But a mutable class is different:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class Session: Sendable {
    var token: String?
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is not safe. Two tasks could hold the same &lt;code&gt;Session&lt;/code&gt; instance and mutate &lt;code&gt;token&lt;/code&gt; at the same time.&lt;/p&gt;&lt;p&gt;In cases like this, we could reach for one of these options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Make it a value type&lt;/strong&gt;. If the type doesn&amp;#x27;t need reference semantics, changing it to a struct is often the simplest fix.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Make the class immutable&lt;/strong&gt;. If mutation isn&amp;#x27;t needed after initialization, we can use &lt;code&gt;let&lt;/code&gt; properties on a &lt;code&gt;final class&lt;/code&gt; to satisfy the compiler.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Protect mutable state with an actor&lt;/strong&gt;. When the type genuinely needs mutable state that is accessed from concurrent code, an actor is usually the cleanest solution.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Protect mutable state manually and use @unchecked Sendable&lt;/strong&gt;. When neither of the above options fits, for example when wrapping a C library with its own thread-safety guarantees, we can take responsibility for synchronization ourselves.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Using an actor for mutable shared state&lt;/h3&gt;&lt;p&gt;Actors are often the cleanest solution when a type owns mutable state that needs to be accessed from concurrent code.&lt;/p&gt;&lt;p&gt;Since actors protect their isolated state, we can move that state into one:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor SessionStore {
    private var token: String?

    func updateToken(_ token: String) {
        self.token = token
    }

    func currentToken() -&amp;gt; String? {
        token
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Calls from outside the actor go through &lt;code&gt;await&lt;/code&gt;, which gives Swift a safe place to coordinate access.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let sessionStore = SessionStore()

Task {
    await sessionStore.updateToken(&amp;quot;abc&amp;quot;)
    let token = await sessionStore.currentToken()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Actor instances themselves can be passed between concurrency domains because their mutable state is protected by actor isolation.&lt;/p&gt;&lt;h3&gt;What is @Sendable?&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Sendable&lt;/code&gt; applies to types. &lt;code&gt;@Sendable&lt;/code&gt; applies to function types and closures.&lt;/p&gt;&lt;p&gt;A &lt;code&gt;@Sendable&lt;/code&gt; closure is a closure that can safely be called from concurrent code. This means its captures must also be safe. We&amp;#x27;ll look at &lt;code&gt;@Sendable&lt;/code&gt; in more detail in a separate article.&lt;/p&gt;&lt;h3&gt;What about @unchecked Sendable?&lt;/h3&gt;&lt;p&gt;Sometimes we know a type is safe, but the compiler cannot prove it.&lt;/p&gt;&lt;p&gt;This may happen with reference types that protect their state internally:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class Counter: @unchecked Sendable {
    private let lock = NSLock()
    private var value = 0

    func increment() {
        lock.lock()
        defer { lock.unlock() }
        value += 1
    }

    func currentValue() -&amp;gt; Int {
        lock.lock()
        defer { lock.unlock() }
        return value
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The compiler sees mutable state and cannot verify that the lock is used correctly. By writing &lt;code&gt;@unchecked Sendable&lt;/code&gt;, we take responsibility for that guarantee ourselves.&lt;/p&gt;&lt;p&gt;&lt;code&gt;@unchecked Sendable&lt;/code&gt; should not be used just to make a compiler warning disappear. It does not make the type thread-safe. If the synchronization is wrong, the compiler will trust us anyway.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Using &lt;code&gt;Sendable&lt;/code&gt; can feel strict at first, especially when enabling stricter concurrency checking in an existing project. But the warnings often point to real design questions around ownership, mutation, and shared state.&lt;/p&gt;&lt;p&gt;For most types, conformance requires little or no extra work: structs with sendable properties get it for free, and actors handle mutable shared state cleanly. The cases that require more thought are usually worth the attention.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding privacy manifests in iOS]]></title><description><![CDATA[Learn what privacy manifests are and how they relate to third-party SDKs and required reason APIs.]]></description><link>https://tanaschita.com/ios-privacy-manifests</link><guid isPermaLink="false">https://tanaschita.com/ios-privacy-manifests</guid><pubDate>Sun, 28 Jun 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many iOS apps include third-party dependencies, such as analytics SDKs, networking libraries or crash reporting tools.
Some of these dependencies may collect data or access privacy-sensitive APIs.&lt;/p&gt;&lt;p&gt;Privacy manifests help make this behavior more explicit. In this article, we&amp;#x27;ll look at what privacy manifests are, how they relate to third-party SDKs and what required reason APIs mean for privacy declarations.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is a privacy manifest?&lt;/h3&gt;&lt;p&gt;A privacy manifest is a file that describes privacy-relevant behavior of an app or SDK in a standard format.&lt;/p&gt;&lt;p&gt;The file is called &lt;code&gt;PrivacyInfo.xcprivacy&lt;/code&gt; and is added to the app target or to an SDK. It can declare information such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;what types of data are collected&lt;/li&gt;&lt;li&gt;whether collected data is linked to the user&lt;/li&gt;&lt;li&gt;whether collected data is used for tracking&lt;/li&gt;&lt;li&gt;which required reason APIs are accessed&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To add one in Xcode, we can select &lt;code&gt;File&lt;/code&gt; -&amp;gt; &lt;code&gt;New&lt;/code&gt; -&amp;gt; &lt;code&gt;File&lt;/code&gt; and choose the &lt;code&gt;App Privacy&lt;/code&gt; file template. Xcode creates a file named &lt;code&gt;PrivacyInfo.xcprivacy&lt;/code&gt;, which should be included in the target membership of the app or SDK that declares the privacy behavior.&lt;/p&gt;&lt;p&gt;The privacy manifest itself does not collect data or change the runtime behavior of an app. It is a declaration file that helps describe what the app or SDK does from a privacy perspective.&lt;/p&gt;&lt;p&gt;For example, if an app sends a user’s email address to a backend for account functionality and uses &lt;code&gt;UserDefaults&lt;/code&gt; for storing app settings, the manifest could contain entries like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;NSPrivacyTracking&amp;lt;/key&amp;gt;
&amp;lt;false/&amp;gt;
&amp;lt;key&amp;gt;NSPrivacyCollectedDataTypes&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;dict&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyCollectedDataType&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;NSPrivacyCollectedDataTypeEmailAddress&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyCollectedDataTypeLinked&amp;lt;/key&amp;gt;
        &amp;lt;true/&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyCollectedDataTypeTracking&amp;lt;/key&amp;gt;
        &amp;lt;false/&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyCollectedDataTypePurposes&amp;lt;/key&amp;gt;
        &amp;lt;array&amp;gt;
            &amp;lt;string&amp;gt;NSPrivacyCollectedDataTypePurposeAppFunctionality&amp;lt;/string&amp;gt;
        &amp;lt;/array&amp;gt;
    &amp;lt;/dict&amp;gt;
&amp;lt;/array&amp;gt;
&amp;lt;key&amp;gt;NSPrivacyAccessedAPITypes&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;dict&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyAccessedAPIType&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;NSPrivacyAccessedAPICategoryUserDefaults&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyAccessedAPITypeReasons&amp;lt;/key&amp;gt;
        &amp;lt;array&amp;gt;
            &amp;lt;string&amp;gt;CA92.1&amp;lt;/string&amp;gt;
        &amp;lt;/array&amp;gt;
    &amp;lt;/dict&amp;gt;
&amp;lt;/array&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This example declares that the app does not use the collected data for tracking, collects an email address for app functionality and accesses the &lt;code&gt;UserDefaults&lt;/code&gt; required reason API category.&lt;/p&gt;&lt;p&gt;In Xcode, the file can be edited as a property list, so we usually don&amp;#x27;t have to write the XML manually.&lt;/p&gt;&lt;h3&gt;Privacy manifests and third-party SDKs&lt;/h3&gt;&lt;p&gt;Privacy manifests are especially important for third-party SDKs because the privacy behavior of an app is not limited to its own source code.&lt;/p&gt;&lt;p&gt;If we integrate an analytics SDK, a crash reporting SDK, an authentication SDK or another dependency, that code may also collect data or access privacy-sensitive APIs. A privacy manifest makes this more visible by showing what the SDK declares and whether it matches how we use the SDK in the app.&lt;/p&gt;&lt;h3&gt;Generating a privacy report in Xcode&lt;/h3&gt;&lt;p&gt;After archiving an app, we can use Xcode to generate a privacy report.&lt;/p&gt;&lt;p&gt;The privacy report is a developer-facing summary. It helps us inspect the privacy declarations from the app and its embedded dependencies before submitting the app. This information can be used to keep the App Store privacy labels accurate.&lt;/p&gt;&lt;h3&gt;Required reason APIs&lt;/h3&gt;&lt;p&gt;Required reason APIs are APIs that Apple considers privacy-sensitive because they can potentially be used for fingerprinting.&lt;/p&gt;&lt;p&gt;Fingerprinting means collecting signals about a device or user and combining them to identify that user across apps or websites. Some APIs do not look obviously privacy-related at first, but can still reveal useful device-specific information when combined with other signals.&lt;/p&gt;&lt;p&gt;Examples of required reason API categories include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;file timestamp APIs&lt;/li&gt;&lt;li&gt;system boot time APIs&lt;/li&gt;&lt;li&gt;disk space APIs&lt;/li&gt;&lt;li&gt;active keyboard APIs&lt;/li&gt;&lt;li&gt;user defaults APIs&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If an app or SDK uses one of these APIs, the privacy manifest must declare the API category and provide an approved reason for using it.&lt;/p&gt;&lt;p&gt;An example declaration for using &lt;code&gt;UserDefaults&lt;/code&gt; might look like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;NSPrivacyAccessedAPITypes&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;dict&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyAccessedAPIType&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;NSPrivacyAccessedAPICategoryUserDefaults&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;NSPrivacyAccessedAPITypeReasons&amp;lt;/key&amp;gt;
        &amp;lt;array&amp;gt;
            &amp;lt;string&amp;gt;CA92.1&amp;lt;/string&amp;gt;
        &amp;lt;/array&amp;gt;
    &amp;lt;/dict&amp;gt;
&amp;lt;/array&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;NSPrivacyAccessedAPICategoryUserDefaults&lt;/code&gt; declares the accessed API category, while &lt;code&gt;CA92.1&lt;/code&gt; is the approved reason code. In this case, &lt;code&gt;CA92.1&lt;/code&gt; means that the app accesses &lt;code&gt;UserDefaults&lt;/code&gt; to read or write information that is only used by the app itself, for example local settings or preferences.&lt;/p&gt;&lt;p&gt;This is also relevant for third-party SDKs. Even if our own app code does not directly access a required reason API, an SDK might. In that case, the SDK&amp;#x27;s privacy manifest should declare that usage.&lt;/p&gt;</content:encoded><category>security</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Getting started with Apple's Foundation Models framework]]></title><description><![CDATA[Learn what Apple's Foundation Models framework is, how it works, and when to use it.]]></description><link>https://tanaschita.com/foundation-models-getting-started</link><guid isPermaLink="false">https://tanaschita.com/foundation-models-getting-started</guid><pubDate>Sun, 21 Jun 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple&amp;#x27;s Foundation Models framework gives us access to language models directly through a native Swift API. In this article, we&amp;#x27;ll answer some of the questions that naturally come up when using it for the first time.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Getting started is surprisingly simple:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import FoundationModels

let model = SystemLanguageModel.default

guard case .available = model.availability else {
    print(&amp;quot;Apple Intelligence not available on this device&amp;quot;)
    return
}

do {
    let session = LanguageModelSession()
    let response = try await session.respond(to: &amp;quot;Suggest a name for a hiking app.&amp;quot;)
    print(response.content)
} catch {
    print(error.localizedDescription)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;#x27;s all it takes to get a response.&lt;/p&gt;&lt;p&gt;Foundation Models requires an Apple Intelligence-capable device. This includes iPhone 15 Pro and newer, M-series iPads, and Apple Silicon Macs.&lt;/p&gt;&lt;p&gt;&lt;code&gt;LanguageModelSession&lt;/code&gt; maintains conversation history across multiple calls, so we can build multi-turn interactions by reusing the same session instance.&lt;/p&gt;&lt;h3&gt;Structured output&lt;/h3&gt;&lt;p&gt;One area where Foundation Models becomes especially interesting is structured output. Using the &lt;code&gt;@Generable&lt;/code&gt; macro, we can define the exact Swift type we expect and have the model generate instances of that type directly.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Generable
struct AppNameSuggestion {
    let name: String
    let tagline: String
}

let session = LanguageModelSession()
let response = try await session.respond(
    to: &amp;quot;Suggest a name for a hiking app.&amp;quot;,
    generating: AppNameSuggestion.self
)

print(response.content.name)     // e.g. &amp;quot;TrailQuest&amp;quot;
print(response.content.tagline)  // e.g. &amp;quot;Every path, perfectly planned.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The model generates a valid instance of &lt;code&gt;AppNameSuggestion&lt;/code&gt; directly, with no JSON parsing or string extraction involved.&lt;/p&gt;&lt;h3&gt;Where does the model run?&lt;/h3&gt;&lt;p&gt;By default, Foundation Models uses Apple&amp;#x27;s on-device model. Nothing is sent to a server and the framework continues to work without an internet connection. This makes it a particularly interesting option for privacy-sensitive features.&lt;/p&gt;&lt;p&gt;In iOS 27, Apple expanded Foundation Models with support for Apple&amp;#x27;s server-side Private Cloud Compute (PCC) models, accessible through the same API. PCC offers a larger context window and reasoning support, but requires an internet connection and has a daily usage limit. It is currently only available to apps enrolled in the App Store Small Business Program with fewer than 2 million total first-time downloads.&lt;/p&gt;&lt;h3&gt;Can we use third-party models through the same API?&lt;/h3&gt;&lt;p&gt;With iOS 27, Foundation Models can work with third-party model providers. Providers can ship Swift packages that conform to the &lt;code&gt;LanguageModel&lt;/code&gt; protocol. Once a model is created, we can pass it into &lt;code&gt;LanguageModelSession&lt;/code&gt; and continue using the same session API.&lt;/p&gt;&lt;p&gt;The exact setup depends on the provider. For example, a cloud model usually still requires provider-specific configuration such as an API key, model identifier, or account setup. The benefit is that the rest of the app can continue to work with &lt;code&gt;LanguageModelSession&lt;/code&gt; instead of being tightly coupled to one provider.&lt;/p&gt;&lt;p&gt;A third-party model might be configured as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import FoundationModels
import ClaudeForFoundationModels

let model = ClaudeLanguageModel(name: .sonnet4_6, auth: .apiKey(...))
let session = LanguageModelSession(model: model)
let response = try await session.respond(to: &amp;quot;...&amp;quot;)
print(response.content)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;How good are the responses?&lt;/h3&gt;&lt;p&gt;It depends on the task. The on-device model is significantly smaller than models like GPT-4 or Claude. For focused, well-scoped tasks it performs well for example for summarizing short text, classifying or tagging content, extracting structured data or generating short suggestions.&lt;/p&gt;&lt;p&gt;For broader reasoning, general world knowledge, or nuanced writing, it will fall short of larger cloud models. The model is not designed to compete with those larger models, it&amp;#x27;s designed to be fast, private, and always available.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Foundation Models makes it easy to add AI-powered features without signing up for a third-party API, managing keys, or sending user data to a server.&lt;/p&gt;&lt;p&gt;For complex reasoning or broad world knowledge, a larger cloud model is still the better choice. But for focused tasks where privacy and offline support matter, it&amp;#x27;s one of the most practical options iOS developers currently have.&lt;/p&gt;</content:encoded><category>ai</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Async cleanup with defer in Swift]]></title><description><![CDATA[Learn how to perform asynchronous cleanup operations using Swift's async defer support.]]></description><link>https://tanaschita.com/swift-defer-async</link><guid isPermaLink="false">https://tanaschita.com/swift-defer-async</guid><pubDate>Sat, 13 Jun 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s &lt;code&gt;defer&lt;/code&gt; statement is useful for cleanup logic. It guarantees that a block of code runs when the current scope exits, regardless of whether execution completes normally, throws an error, or returns early.&lt;/p&gt;&lt;p&gt;Before Swift 6.4, we couldn&amp;#x27;t perform asynchronous operations directly inside a &lt;code&gt;defer&lt;/code&gt; block. Starting with Swift 6.4, it is possible.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The limitation of defer without async support&lt;/h3&gt;&lt;p&gt;Consider a function that opens a resource and needs to close it once the work is finished:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func processFile() async throws {
    let file = try await openFile()

    defer {
        file.close()
    }

    try await process(file)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works as long as cleanup is synchronous. However, many modern APIs are asynchronous. If closing the file requires an &lt;code&gt;await&lt;/code&gt;, the code above no longer works:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;defer {
    await file.close() // Compiler error on Swift version &amp;lt; 6.4
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To solve the compiler error, we had to move the cleanup logic elsewhere:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let file = try await openFile()

do {
    try await process(file)
    await file.close()
} catch {
    await file.close()
    throw error
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Besides introducing duplication, this makes the cleanup logic less obvious because it is separated from the resource acquisition.&lt;/p&gt;&lt;h3&gt;Async cleanup with defer&lt;/h3&gt;&lt;p&gt;With defer supporting asynchronous operations, we can keep acquisition and cleanup next to each other:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func processFile() async throws {
    let file = try await openFile()

    defer {
        await file.close()
    }

    try await process(file)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The cleanup operation is now guaranteed to run when the scope exits, while still supporting asynchronous work.&lt;/p&gt;&lt;h3&gt;Note on actor isolation&lt;/h3&gt;&lt;p&gt;A &lt;code&gt;defer&lt;/code&gt; block inherits the actor isolation of its enclosing scope. If the surrounding function runs on &lt;code&gt;@MainActor&lt;/code&gt;, the cleanup code inside defer also runs on &lt;code&gt;@MainActor&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding composition in SwiftUI]]></title><description><![CDATA[Understand how SwiftUI builds and represents view hierarchies.]]></description><link>https://tanaschita.com/swiftui-understanding-composition</link><guid isPermaLink="false">https://tanaschita.com/swiftui-understanding-composition</guid><pubDate>Sun, 07 Jun 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with SwiftUI, we constantly compose views from other views.&lt;/p&gt;&lt;p&gt;A &lt;code&gt;Text&lt;/code&gt; view can be placed inside a &lt;code&gt;VStack&lt;/code&gt;, a &lt;code&gt;VStack&lt;/code&gt; can be embedded in a &lt;code&gt;NavigationStack&lt;/code&gt;, and an entire screen can become a child of another view. This ability to build complex user interfaces from smaller building blocks is one of the core ideas behind SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s take a closer look at why this works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Everything is a view&lt;/h3&gt;&lt;p&gt;Whether we create a simple &lt;code&gt;Text&lt;/code&gt; view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;Hello World&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or a more complex container:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack {
    Text(&amp;quot;Title&amp;quot;)
    Text(&amp;quot;Subtitle&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;both conform to the &lt;code&gt;View&lt;/code&gt; protocol. This means SwiftUI can treat simple and complex views in exactly the same way. From the framework&amp;#x27;s perspective, both examples are simply views.&lt;/p&gt;&lt;p&gt;The same principle applies to custom views. Everything participates in the hierarchy through the same abstraction.&lt;/p&gt;&lt;p&gt;One thing that makes this work in practice is &lt;code&gt;@ViewBuilder&lt;/code&gt;, a result builder that allows us to write multiple child views inside a closure without explicit return statements. It&amp;#x27;s what makes the familiar SwiftUI syntax possible and what allows containers like &lt;code&gt;VStack&lt;/code&gt; to accept multiple views as children.&lt;/p&gt;&lt;h3&gt;A tree of views&lt;/h3&gt;&lt;p&gt;One way to think about a SwiftUI interface is as a tree.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;NavigationStack {
    VStack {
        Text(&amp;quot;Title&amp;quot;)

        HStack {
            Text(&amp;quot;Author&amp;quot;)
            Spacer()
            Text(&amp;quot;Date&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;creates a hierarchy similar to:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NavigationStack
└── VStack
    ├── Text
    └── HStack
        ├── Text
        ├── Spacer
        └── Text
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each node in the tree is itself a view. Some views are simple leaf nodes such as &lt;code&gt;Text&lt;/code&gt; or &lt;code&gt;Spacer&lt;/code&gt;. Others act as containers that group multiple child views together.&lt;/p&gt;&lt;p&gt;Because they all conform to the same protocol, SwiftUI can traverse and update the hierarchy in a uniform way.&lt;/p&gt;&lt;h3&gt;The Composite pattern&lt;/h3&gt;&lt;p&gt;This idea is closely related to the Composite pattern in software design, a design pattern where individual objects and groups of objects are treated through the same abstraction.&lt;/p&gt;&lt;p&gt;In SwiftUI:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Text&lt;/code&gt; acts as a leaf&lt;/li&gt;&lt;li&gt;containers such as &lt;code&gt;VStack&lt;/code&gt; and &lt;code&gt;HStack&lt;/code&gt; act as composites&lt;/li&gt;&lt;li&gt;both conform to &lt;code&gt;View&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As a result, complex user interfaces can be built from smaller pieces without requiring different APIs for individual views and groups of views.&lt;/p&gt;&lt;h3&gt;Why composition matters&lt;/h3&gt;&lt;p&gt;Composition is one of the main reasons SwiftUI scales so well. To see why, consider a screen built as a single large view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ArticleListView: View {
    let articles: [Article]

    var body: some View {
        List(articles) { article in
            VStack(alignment: .leading) {
                Text(article.title)
                    .font(.headline)
                Text(article.author)
                    .font(.subheadline)
                Text(article.date)
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As the row grows more complex, the entire screen becomes harder to read and the row layout becomes impossible to reuse elsewhere. Extracting it into its own component solves both problems:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ArticleRowView: View {
    let article: Article

    var body: some View {
        VStack(alignment: .leading) {
            Text(article.title)
                .font(.headline)
            Text(article.author)
                .font(.subheadline)
            Text(article.date)
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    }
}

struct ArticleListView: View {
    let articles: [Article]

    var body: some View {
        List(articles) { article in
            ArticleRowView(article: article)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each component now has a single responsibility, and &lt;code&gt;ArticleRowView&lt;/code&gt; can be reused in a search results screen, a featured section, or anywhere else that needs to display an article.&lt;/p&gt;&lt;p&gt;This is composition in practice: breaking interfaces into small focused views and combining them into larger structures.&lt;/p&gt;&lt;h3&gt;Why SwiftUI doesn&amp;#x27;t just store child views&lt;/h3&gt;&lt;p&gt;At this point, we might wonder why SwiftUI uses concepts such as &lt;code&gt;View&lt;/code&gt;, &lt;code&gt;some View&lt;/code&gt;, and &lt;code&gt;@ViewBuilder&lt;/code&gt; instead of a simpler approach like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol View {
    var children: [any View] { get }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Rather than storing view hierarchies as collections of child views, SwiftUI uses Swift&amp;#x27;s type system to represent much of the hierarchy at compile time. For example, when we write:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack {
    Text(&amp;quot;Title&amp;quot;)
    Text(&amp;quot;Subtitle&amp;quot;)
    Image(...)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;the &lt;code&gt;@ViewBuilder&lt;/code&gt; transforms the child views into a concrete type that becomes part of the view hierarchy itself. Multiple child views are represented as tuple-based view content, simplified such as:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;TupleView&amp;lt;(Text, Text, Image)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Unlike an array of &lt;code&gt;any View&lt;/code&gt;, the resulting type still contains information about the exact child views that make up the hierarchy. With this type information available, the framework can reason about the structure of the hierarchy more efficiently and preserve type safety throughout the view tree.&lt;/p&gt;&lt;p&gt;This also helps explain why &lt;code&gt;some View&lt;/code&gt; fits SwiftUI so well. Each view&amp;#x27;s body produces a specific concrete type, but that type can quickly become long and implementation-specific. By returning &lt;code&gt;some View&lt;/code&gt;, we tell the compiler that the body returns one specific view type, without having to name that type explicitly.&lt;/p&gt;&lt;p&gt;This tuple-based design also had an interesting historical consequence. Early SwiftUI versions only supported up to ten direct child views inside a &lt;code&gt;@ViewBuilder&lt;/code&gt; closure, because each possible child count required its own explicit implementation. If we needed more, we had to split the children into another container, such as &lt;code&gt;Group&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack {
    Text(&amp;quot;1&amp;quot;)
    Text(&amp;quot;2&amp;quot;)
    // ...

    Group {
        Text(&amp;quot;11&amp;quot;)
        Text(&amp;quot;12&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As Swift evolved, in part through variadic generics introduced in Swift 5.9, this limitation was lifted. Modern SwiftUI can represent arbitrarily large groups of child views without requiring &lt;code&gt;Group&lt;/code&gt; as a workaround.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Composition is one of the foundational ideas behind SwiftUI.&lt;/p&gt;&lt;p&gt;By treating simple views, custom components, and entire view hierarchies through the same &lt;code&gt;View&lt;/code&gt; abstraction, SwiftUI makes it easy to build complex interfaces from small reusable building blocks.&lt;/p&gt;&lt;p&gt;Under the hood, the framework goes even further by representing much of the hierarchy through Swift&amp;#x27;s type system. While most of these details remain invisible in day-to-day development, understanding them helps explain why SwiftUI&amp;#x27;s compositional model feels both flexible and powerful.&lt;/p&gt;</content:encoded><category>design patterns</category><category>architecture</category><category>swiftui</category></item><item><title><![CDATA[Registering for push notifications in SwiftUI]]></title><description><![CDATA[Learn how to request notification permissions and register for remote notifications in a SwiftUI app.]]></description><link>https://tanaschita.com/ios-notifications-registering-in-swiftui</link><guid isPermaLink="false">https://tanaschita.com/ios-notifications-registering-in-swiftui</guid><pubDate>Sun, 31 May 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Push notifications allow us to inform users about updates even when they are not actively using the app.&lt;/p&gt;&lt;p&gt;Before an iOS app can receive remote notifications, we first need to register the app with Apple Push Notification service (APNs). This process includes requesting notification permission from the user, registering with APNs, and handling the generated device token.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to set up push notification registration in a SwiftUI app.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Overview&lt;/h3&gt;&lt;p&gt;To register for push notifications, we need to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;enable the push notifications capability&lt;/li&gt;&lt;li&gt;request notification permission&lt;/li&gt;&lt;li&gt;register with APNs&lt;/li&gt;&lt;li&gt;handle the generated device token&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s go through each step.&lt;/p&gt;&lt;h3&gt;Enabling the push notifications capability&lt;/h3&gt;&lt;p&gt;To enable push notifications, we first need to add the Push Notifications capability to the app target.&lt;/p&gt;&lt;p&gt;In Xcode, open the &lt;code&gt;Signing &amp;amp; Capabilities&lt;/code&gt; tab and add the &lt;code&gt;Push Notifications&lt;/code&gt; capability. This configures the required entitlement for the app and enables communication with APNs.&lt;/p&gt;&lt;h3&gt;Requesting notification permission&lt;/h3&gt;&lt;p&gt;Before the app can display notifications, the user needs to grant permission.&lt;/p&gt;&lt;p&gt;We can request authorization using &lt;code&gt;UNUserNotificationCenter&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

func requestNotificationPermission() async throws -&amp;gt; Bool {
    try await UNUserNotificationCenter.current().requestAuthorization(
        options: [.alert, .badge, .sound]
    )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The authorization options define which notification features the app would like to use:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.alert&lt;/code&gt;: display alerts&lt;/li&gt;&lt;li&gt;&lt;code&gt;.badge&lt;/code&gt;: update the app icon badge&lt;/li&gt;&lt;li&gt;&lt;code&gt;.sound&lt;/code&gt;: play sounds&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In practice, it is usually best to request permission in context, for example when the user enables a feature that benefits from notifications, instead of immediately on app launch.&lt;/p&gt;&lt;h4&gt;Checking existing authorization status&lt;/h4&gt;&lt;p&gt;Before prompting, it&amp;#x27;s worth checking whether the user has already made a decision. This avoids redundant permission requests and lets us call &lt;code&gt;registerForRemoteNotifications()&lt;/code&gt; directly if permission has already been granted:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func configureNotifications() async throws {
    let center = UNUserNotificationCenter.current()
    let settings = await center.notificationSettings()

    switch settings.authorizationStatus {
    case .notDetermined:
        let granted = try await center.requestAuthorization(options: [.alert, .badge, .sound])
        if granted {
            await registerForRemoteNotifications()
        }
    case .authorized, .provisional:
        await registerForRemoteNotifications()
    default:
        break
    }
}

@MainActor
func registerForRemoteNotifications() {
    UIApplication.shared.registerForRemoteNotifications()
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Registering with APNs&lt;/h3&gt;&lt;p&gt;Once permission has been granted, we can register the app with APNs:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;UIApplication.shared.registerForRemoteNotifications()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This starts the registration process with Apple Push Notification service.&lt;/p&gt;&lt;p&gt;If registration succeeds, iOS generates a device token and passes it back to the app.&lt;/p&gt;&lt;h3&gt;Integrating an AppDelegate in SwiftUI&lt;/h3&gt;&lt;p&gt;Even in SwiftUI apps, remote notification registration callbacks are still delivered through the app delegate.&lt;/p&gt;&lt;p&gt;To integrate an app delegate into a SwiftUI app, we can use &lt;code&gt;UIApplicationDelegateAdaptor&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct ExampleApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self)
    private var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Receiving the device token&lt;/h3&gt;&lt;p&gt;Once registration succeeds, the app delegate receives the generated device token:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class AppDelegate: NSObject, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        let tokenString = deviceToken.map { String(format: &amp;quot;%02.2hhx&amp;quot;, $0) }.joined()
        print(&amp;quot;Device token: \(tokenString)&amp;quot;)

        // Send tokenString to your server
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The device token is provided as &lt;code&gt;Data&lt;/code&gt;, so we need to convert it to a hex string before sending it to a backend. The snippet above maps each byte to a zero-padded two-digit hex representation and joins them into a single string, which is the format most push notification services expect.&lt;/p&gt;&lt;p&gt;The token itself uniquely identifies the app installation on the device. In most cases it is forwarded to a backend server, which then uses it to send push notifications through APNs.&lt;/p&gt;&lt;h3&gt;Handling registration failures&lt;/h3&gt;&lt;p&gt;If registration fails, iOS calls a different app delegate method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func application(
    _ application: UIApplication,
    didFailToRegisterForRemoteNotificationsWithError error: Error
) {
    print(&amp;quot;Failed to register: \(error)&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This can happen, for example, if push notifications are unavailable on the device or if the simulator is used.&lt;/p&gt;&lt;h3&gt;Testing push notification registration&lt;/h3&gt;&lt;p&gt;Notification permissions and notification handling can both be tested in the simulator.&lt;/p&gt;&lt;p&gt;Xcode allows us to simulate remote notifications by sending &lt;code&gt;.apns&lt;/code&gt; payload files to the app. This is useful for testing notification UI, payload handling, and navigation flows during development.&lt;/p&gt;&lt;p&gt;However, the simulator is not fully registered with Apple Push Notification service in the same way as a physical device. For example, real APNs delivery and production device token workflows should still be tested on actual hardware.&lt;/p&gt;&lt;h3&gt;A note on device tokens&lt;/h3&gt;&lt;p&gt;Device tokens are not guaranteed to stay the same forever.&lt;/p&gt;&lt;p&gt;For example, they can change after:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;reinstalling the app&lt;/li&gt;&lt;li&gt;restoring a device backup&lt;/li&gt;&lt;li&gt;switching devices&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Because of this, apps should treat the device token as dynamic and keep the backend updated whenever a new token is received.&lt;/p&gt;&lt;h3&gt;A note on provisional authorization&lt;/h3&gt;&lt;p&gt;There is an additional authorization option worth knowing about: &lt;code&gt;.provisional&lt;/code&gt;. It allows the app to deliver quiet notifications to Notification Center without showing the standard permission prompt upfront. The user can then decide whether to keep receiving notifications or turn them off.&lt;/p&gt;&lt;p&gt;While this can be useful for certain strategies, it is worth testing carefully. Provisional notifications are delivered quietly by default, and the exact upgrade path to prominent notifications can feel less predictable across iOS versions and user settings.&lt;/p&gt;</content:encoded><category>notifications</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Working with the Keychain in iOS]]></title><description><![CDATA[Learn how to securely store sensitive data using Apple’s Keychain Services API.]]></description><link>https://tanaschita.com/ios-keychain-secure-data-storage</link><guid isPermaLink="false">https://tanaschita.com/ios-keychain-secure-data-storage</guid><pubDate>Sun, 24 May 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developing iOS apps, we often need to persist sensitive user data such as passwords, authentication tokens, or cryptographic keys.&lt;/p&gt;&lt;p&gt;For this type of data, storing values in plain files or &lt;code&gt;UserDefaults&lt;/code&gt; is usually not appropriate. Instead, iOS provides the Keychain Services API, which stores sensitive data in an encrypted and system-managed database.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how the Keychain API works and how to save, retrieve, update, and delete keychain items in Swift.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;A note on the API design&lt;/h3&gt;&lt;p&gt;The Keychain Services API is a C-based API, which makes it feel less modern compared to many Swift APIs.&lt;/p&gt;&lt;p&gt;In practice, many apps wrap the API in a small Swift abstraction or use a third-party library to simplify common operations.&lt;/p&gt;&lt;p&gt;That said, understanding the underlying query structure is still valuable because most abstractions are ultimately built on top of the same API.&lt;/p&gt;&lt;h3&gt;Understanding keychain queries&lt;/h3&gt;&lt;p&gt;The Keychain Services API is based around queries.&lt;/p&gt;&lt;p&gt;A keychain query is a dictionary of key-value pairs that describes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the type of item we want to work with&lt;/li&gt;&lt;li&gt;how the item can be identified&lt;/li&gt;&lt;li&gt;additional configuration or access options&lt;/li&gt;&lt;li&gt;the data itself&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The same query structure is used for saving, reading, updating, and deleting items.&lt;/p&gt;&lt;h3&gt;Saving keychain items&lt;/h3&gt;&lt;p&gt;For example, to store a password in the keychain, we first create a query describing the item we want to save:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let query: [CFString: Any] = [
    kSecClass:  kSecClassGenericPassword,
    kSecAttrService: &amp;quot;com.app.service&amp;quot;,
    kSecAttrAccount: username,
    kSecValueData: passwordData]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s break this down.&lt;/p&gt;&lt;h4&gt;1. Defining the item type &lt;code&gt;kSecClass: kSecClassGenericPassword&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;&lt;code&gt;kSecClass&lt;/code&gt; defines the type of keychain item we want to store. In this case, we use &lt;code&gt;kSecClassGenericPassword&lt;/code&gt; for storing a password. Other available types include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;kSecClassInternetPassword&lt;/code&gt; to store Internet passwords.&lt;/li&gt;&lt;li&gt;&lt;code&gt;kSecClassCertificate&lt;/code&gt; to store certificates.&lt;/li&gt;&lt;li&gt;&lt;code&gt;kSecClassKey&lt;/code&gt; to store cryptographic keys.&lt;/li&gt;&lt;li&gt;&lt;code&gt;kSecClassIdentity&lt;/code&gt; to store identities.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The item type determines which attributes are available for the query.&lt;/p&gt;&lt;h4&gt;2. Defining a namespace &lt;code&gt;kSecAttrService: &amp;quot;com.app.service&amp;quot;&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;A string that namespaces your keychain items, typically the app&amp;#x27;s bundle identifier or a specific service name. This is important for avoiding collisions when multiple items share the same account name.&lt;/p&gt;&lt;h4&gt;3. Defining item attributes &lt;code&gt;kSecAttrAccount: username&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;Attributes are used to identify the item and provide additional metadata.&lt;/p&gt;&lt;p&gt;In this example, &lt;code&gt;kSecAttrAccount&lt;/code&gt; stores the username associated with the password.&lt;/p&gt;&lt;p&gt;We can additionally provide extra query arguments to configure how the keychain item behaves. For example &lt;code&gt;kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked&lt;/code&gt;, which controls when the item can be accessed. &lt;code&gt;kSecAttrAccessibleWhenUnlocked&lt;/code&gt; ensures that the item is only accessible while the device is unlocked.&lt;/p&gt;&lt;h4&gt;4. Providing the sensitive data &lt;code&gt;kSecValueData: passwordData&lt;/code&gt;&lt;/h4&gt;&lt;p&gt;The sensitive value itself is stored using &lt;code&gt;kSecValueData&lt;/code&gt;. The value must be provided as &lt;code&gt;Data&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Adding the item to the keychain&lt;/h3&gt;&lt;p&gt;Once the query is ready, we can store the item using &lt;code&gt;SecItemAdd&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
    throw KeychainError.errorWithStatus(status)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The method returns a status code indicating whether the operation succeeded.&lt;/p&gt;&lt;p&gt;One error to watch our for is the error &lt;code&gt;errSecDuplicateItem&lt;/code&gt;, which occurs when an item with the same attributes already exists. We can handle this by checking for that status and calling &lt;code&gt;SecItemUpdate&lt;/code&gt; instead (see the update section below), or by deleting the old item before adding the new one.&lt;/p&gt;&lt;h3&gt;Retrieving keychain items&lt;/h3&gt;&lt;p&gt;To read a password from the keychain, we can create another query:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: &amp;quot;com.app.service&amp;quot;,
    kSecAttrAccount: username,
    kSecReturnData: true]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The key difference is &lt;code&gt;kSecReturnData: true&lt;/code&gt; which tells the Keychain API that we want the stored value returned.&lt;/p&gt;&lt;p&gt;We can then retrieve the item using &lt;code&gt;SecItemCopyMatching&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &amp;amp;result)

guard status != errSecItemNotFound else {
    throw KeychainError.noPassword
}
guard status == errSecSuccess else {
    throw KeychainError.errorWithStatus(status)
}
guard let passwordData = result as? Data else {
    throw KeychainError.unexpectedPasswordData
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once we have the data, we can convert it back into a &lt;code&gt;String&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let password = String(data: passwordData, encoding: .utf8)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Updating keychain items&lt;/h3&gt;&lt;p&gt;To update an existing item, we first define a query that identifies the item and then provide the updated values separately:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: &amp;quot;com.app.service&amp;quot;,
    kSecAttrAccount: username
]
let attributes: [CFString: Any] = [
    kSecValueData: newPasswordData
]
let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The query identifies which item should be updated, while the attributes dictionary contains the new values.&lt;/p&gt;&lt;h3&gt;Deleting keychain items&lt;/h3&gt;&lt;p&gt;Deleting an item uses the same query-based approach:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let query: [CFString: Any] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: &amp;quot;com.app.service&amp;quot;,
    kSecAttrAccount: username
]
let status = SecItemDelete(query as CFDictionary)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the operation succeeds, the item is permanently removed from the keychain.&lt;/p&gt;</content:encoded><category>security</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding basic animations in SwiftUI]]></title><description><![CDATA[Learn how to animate state changes in SwiftUI views.]]></description><link>https://tanaschita.com/swiftui-basic-animations</link><guid isPermaLink="false">https://tanaschita.com/swiftui-basic-animations</guid><pubDate>Sat, 16 May 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI provides built-in support for animations. In many cases, adding animations only requires attaching an animation to a state change.&lt;/p&gt;&lt;p&gt;There are two common ways to do this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;code&gt;animation(_:value:)&lt;/code&gt; view modifier for animations when a certain state value changes&lt;/li&gt;&lt;li&gt;&lt;code&gt;withAnimation(_:_:)&lt;/code&gt; global function which we can use to apply state changes and create animations for all views affected by them&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how both approaches work and when to use each of them.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Animating a view with &lt;code&gt;animation(_:value:)&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;animation(_:value:)&lt;/code&gt; view modifier is useful when we need to animate a view every time a specific value changes. We can use it as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var color = Color.brown

var body: some View {
    Button(&amp;quot;Change color&amp;quot;) {
        color = color == .brown ? .mint : .brown
    }
    .padding(20)
    .background(color)
    .foregroundStyle(.white)
    .animation(.easeInOut, value: color)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The modifier does not animate the value itself. The value parameter defines which state change should trigger the animation. Whenever the value of color changes, SwiftUI animates all affected updates.&lt;/p&gt;&lt;p&gt;This type of animation is often referred to as an implicit animation because the view automatically animates when the specified state changes.&lt;/p&gt;&lt;p&gt;We can also disable animation for a view by passing &lt;code&gt;nil&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.animation(nil, value: color)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is handy when we want to opt a specific view out of an animation that would otherwise be inherited from a parent.&lt;/p&gt;&lt;h3&gt;Animating state changes with &lt;code&gt;withAnimation&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Instead of attaching the animation to a view, we can also explicitly animate state changes by using the &lt;code&gt;withAnimation&lt;/code&gt; global function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var color = Color.brown

var body: some View {
    Button(&amp;quot;Change color&amp;quot;) {
        withAnimation(.easeInOut) {
            color = color == .brown ? .mint : .brown
        }
    }
    .padding(20)
    .background(color)
    .foregroundStyle(.white)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this approach, the animation is applied to all view updates caused by the state changes inside the &lt;code&gt;withAnimation&lt;/code&gt; closure.&lt;/p&gt;&lt;h3&gt;Reacting when an animation finishes&lt;/h3&gt;&lt;p&gt;&lt;code&gt;withAnimation&lt;/code&gt; additionally provides a completion handler (iOS 17+), so we can run code once the animation settles:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;withAnimation(.easeInOut) {
    color = color == .brown ? .mint : .brown
} completion: {
    print(&amp;quot;Color animation finished&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is useful for chaining animations or triggering follow-up actions after a transition completes.&lt;/p&gt;&lt;h3&gt;Choosing between both approaches&lt;/h3&gt;&lt;p&gt;Both approaches can often achieve the same visual result, but they are useful in different situations.&lt;/p&gt;&lt;p&gt;&lt;code&gt;animation(_:value:)&lt;/code&gt; works well when a specific view should always animate in response to a particular state change.&lt;/p&gt;&lt;p&gt;&lt;code&gt;withAnimation&lt;/code&gt; is often the better choice when multiple state changes should animate together or when we want more explicit control over when animations occur.&lt;/p&gt;</content:encoded><category>swiftui</category><category>animations</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to present a local HTML file in SwiftUI]]></title><description><![CDATA[Learn how to show a locally stored HTML file with WebView and WKWebView]]></description><link>https://tanaschita.com/swiftui-present-local-html-files</link><guid isPermaLink="false">https://tanaschita.com/swiftui-present-local-html-files</guid><pubDate>Sun, 03 May 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developing iOS apps, we sometimes need to display a locally stored HTML file, for example static help pages, terms and conditions, or release notes bundled with the app. For a long time, the only way to do this in SwiftUI was to wrap Apple&amp;#x27;s &lt;code&gt;WKWebView&lt;/code&gt; using &lt;code&gt;UIViewRepresentable&lt;/code&gt;. Starting with iOS 26, Apple introduced a native &lt;code&gt;WebView&lt;/code&gt; for SwiftUI.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at both approaches:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;iOS 26+&lt;/strong&gt;: the native &lt;code&gt;WebView&lt;/code&gt; and &lt;code&gt;WebPage&lt;/code&gt; from WebKit for SwiftUI&lt;/li&gt;&lt;li&gt;&lt;strong&gt;iOS 14+&lt;/strong&gt;: the classic &lt;code&gt;WKWebView&lt;/code&gt; + &lt;code&gt;UIViewRepresentable&lt;/code&gt; approach for broader compatibility&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;iOS 26+: Using the native WebView&lt;/h3&gt;&lt;p&gt;At WWDC 2025, Apple introduced a native &lt;code&gt;WebView&lt;/code&gt; for SwiftUI. To use it, import &lt;code&gt;WebKit&lt;/code&gt; alongside &lt;code&gt;SwiftUI&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;Loading a local file directly&lt;/h4&gt;&lt;p&gt;The simplest way to show a local HTML file is to resolve its bundle URL and pass it straight to &lt;code&gt;WebView&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import WebKit

struct HTMLView: View {
    let fileName: String

    var body: some View {
        if let url = Bundle.main.url(forResource: fileName, withExtension: &amp;quot;html&amp;quot;) {
            WebView(url: url)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works well for simple HTML files. If the file references additional local resources such as stylesheets or images, using WebPage provides more control over how the content is loaded.&lt;/p&gt;&lt;h4&gt;Using WebPage for more control&lt;/h4&gt;&lt;p&gt;For anything beyond a static display, reading the page title, tracking load progress, or modifying the HTML before showing it, we can use the &lt;code&gt;WebPage&lt;/code&gt; class. &lt;code&gt;WebPage&lt;/code&gt; is an &lt;code&gt;Observable&lt;/code&gt; object that represents and controls the web content:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import WebKit

struct HTMLView: View {
    let fileName: String
    @State private var page = WebPage()

    var body: some View {
        WebView(page)
            .onAppear {
                guard let url = Bundle.main.url(
                    forResource: fileName,
                    withExtension: &amp;quot;html&amp;quot;
                ) else { return }
                page.load(URLRequest(url: url))
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because &lt;code&gt;WebPage&lt;/code&gt; conforms to &lt;code&gt;Observable&lt;/code&gt;, we can read its properties directly in our view. For example, showing a loading indicator while the page loads:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    VStack {
        if page.isLoading {
            ProgressView()
        }
        WebView(page)
    }
    .onAppear {
        guard let url = Bundle.main.url(
            forResource: fileName,
            withExtension: &amp;quot;html&amp;quot;
        ) else { return }
        page.load(URLRequest(url: url))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Loading local assets&lt;/h4&gt;&lt;p&gt;If our HTML file references local resources such as a stylesheet or images, we can load the HTML as a string and pass &lt;code&gt;Bundle.main.resourceURL&lt;/code&gt; as the &lt;code&gt;baseURL&lt;/code&gt;. This tells the web view where to resolve relative paths, so local assets load correctly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.onAppear {
    guard let url = Bundle.main.url(
        forResource: fileName,
        withExtension: &amp;quot;html&amp;quot;
    ) else { return }

    guard let htmlString = try? String(contentsOf: url, encoding: .utf8) else { return }

    page.load(html: htmlString, baseURL: Bundle.main.resourceURL)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we need to process the HTML string programmatically before showing it, we can use the same approach.&lt;/p&gt;&lt;h4&gt;Showing the view in a sheet&lt;/h4&gt;&lt;p&gt;And here&amp;#x27;s an example on how we can show the view in a sheet:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var isTermsSheetPresented = false
@State private var page = WebPage()

var body: some View {
    Button(&amp;quot;Terms &amp;amp; Conditions&amp;quot;) {
        isTermsSheetPresented.toggle()
    }
    .sheet(isPresented: $isTermsSheetPresented) {
        NavigationStack {
            WebView(page)
                .navigationTitle(page.title)
                .navigationBarTitleDisplayMode(.inline)
                .onAppear {
                    guard let url = Bundle.main.url(forResource: &amp;quot;terms&amp;quot;, withExtension: &amp;quot;html&amp;quot;) else { return }
                    page.load(URLRequest(url: url))
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;iOS 14+: Using WKWebView with UIViewRepresentable&lt;/h3&gt;&lt;p&gt;If the app targets iOS versions earlier than iOS 26, we&amp;#x27;ll need to use &lt;code&gt;WKWebView&lt;/code&gt; wrapped in a &lt;code&gt;UIViewRepresentable&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;Creating the wrapper&lt;/h4&gt;&lt;p&gt;Since &lt;code&gt;WKWebView&lt;/code&gt; is a &lt;code&gt;UIView&lt;/code&gt; subclass, we first create a SwiftUI-compatible wrapper:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import WebKit

struct LegacyHTMLView: UIViewRepresentable {
    let fileName: String

    func makeUIView(context: Context) -&amp;gt; WKWebView {
        return WKWebView()
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        guard let url = Bundle.main.url(forResource: fileName, withExtension: &amp;quot;html&amp;quot;) else { return }
        webView.load(URLRequest(url: url))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Loading local assets&lt;/h4&gt;&lt;p&gt;If the HTML file references local assets such as a stylesheet or images, we can use &lt;code&gt;loadFileURL(_:allowingReadAccessTo:)&lt;/code&gt; instead of &lt;code&gt;load(_:)&lt;/code&gt;. This grants the web view read access to the parent directory so relative paths resolve correctly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func updateUIView(_ webView: WKWebView, context: Context) {
    guard let url = Bundle.main.url(forResource: fileName, withExtension: &amp;quot;html&amp;quot;) else { return }
    webView.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent())
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Modifying the HTML before displaying it&lt;/h4&gt;&lt;p&gt;If we need to modify the HTML content programmatically before showing it, we can use &lt;code&gt;loadHTMLString(_:baseURL:)&lt;/code&gt;. Passing the file&amp;#x27;s URL as the &lt;code&gt;baseURL&lt;/code&gt; ensures that relative references still work:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func updateUIView(_ webView: WKWebView, context: Context) {
    guard let url = Bundle.main.url(
        forResource: fileName,
        withExtension: &amp;quot;html&amp;quot;
    ) else { return }

    guard var htmlString = try? String(contentsOf: url, encoding: .utf8) else { return }

    // Modify the HTML string before displaying
    htmlString = htmlString.replacingOccurrences(of: &amp;quot;{{version}}&amp;quot;, with: &amp;quot;2.0&amp;quot;)

    webView.loadHTMLString(htmlString, baseURL: url)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Using the view in a sheet&lt;/h4&gt;&lt;p&gt;And this is how we can use the view in a sheet:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var isTermsSheetPresented = false

var body: some View {
    Button(&amp;quot;Terms &amp;amp; Conditions&amp;quot;) {
        isTermsSheetPresented.toggle()
    }
    .sheet(isPresented: $isTermsSheetPresented) {
        LegacyHTMLView(fileName: &amp;quot;terms&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Bringing it together&lt;/h3&gt;&lt;p&gt;Displaying local HTML in SwiftUI can be implemented in different ways depending on the deployment target.&lt;/p&gt;&lt;p&gt;For newer iOS versions, the native &lt;code&gt;WebView&lt;/code&gt; provides a clean and fully SwiftUI-based solution. For broader compatibility, &lt;code&gt;WKWebView&lt;/code&gt; wrapped in &lt;code&gt;UIViewRepresentable&lt;/code&gt; remains a reliable approach.&lt;/p&gt;&lt;p&gt;We can use a conditional check to get the best of both:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    if #available(iOS 26, *) {
        HTMLView(fileName: fileName)
    } else {
        LegacyHTMLView(fileName: fileName)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both approaches allow us to display bundled HTML files and handle more advanced use cases such as modifying content or loading local resources.&lt;/p&gt;</content:encoded><category>webkit</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to implement pagination with SwiftUI's List view]]></title><description><![CDATA[Learn how to implement infinite scrolling with SwiftUI.]]></description><link>https://tanaschita.com/swiftui-list-pagination</link><guid isPermaLink="false">https://tanaschita.com/swiftui-list-pagination</guid><pubDate>Sun, 26 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with larger datasets, loading everything at once is often unnecessary. In many cases, we want to fetch an initial page of results and then load more content as the user scrolls.&lt;/p&gt;&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;List&lt;/code&gt; works well for this pattern. By combining it with asynchronous loading and a small amount of pagination state, we can build a predictable infinite scrolling experience without much code.&lt;/p&gt;&lt;p&gt;In this article, we’ll look at how to implement pagination with &lt;code&gt;List&lt;/code&gt; by loading the next page once the user reaches the end of the list.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Approach overview&lt;/h3&gt;&lt;p&gt;Pagination can be implemented in different ways. In this article, we focus on infinite scrolling, where new content is loaded automatically as the user reaches the end of the list.&lt;/p&gt;&lt;p&gt;A simple and effective way to achieve this is to add a dedicated &amp;quot;loading row&amp;quot; at the bottom of the list.&lt;/p&gt;&lt;p&gt;When this row becomes visible, we trigger the next page request.&lt;/p&gt;&lt;h3&gt;Implementing the list&lt;/h3&gt;&lt;p&gt;We start by rendering the current items and conditionally adding the loading row:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;List {
    ForEach(viewModel.items, id: \.id) { item in
        ListItemView(item: item)
    }

    if viewModel.isMoreDataAvailable {
        lastRowView
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The key idea here is that the loading row is only shown when more data can be loaded.&lt;/p&gt;&lt;h3&gt;Triggering pagination&lt;/h3&gt;&lt;p&gt;The loading row is responsible for triggering the next page request when it appears on screen:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var lastRowView: some View {
    ZStack {
        switch viewModel.paginationState {
        case .isLoading:
            ProgressView()

        case .error(let error):
            ErrorView(error)
        }
    }
    .frame(height: 50)
    .onAppear {
        viewModel.loadMoreItems()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the user scrolls to the end of the list, this row becomes visible and its &lt;code&gt;onAppear&lt;/code&gt; modifier is triggered.&lt;/p&gt;&lt;p&gt;This is the moment where we load the next page.&lt;/p&gt;&lt;h3&gt;Managing pagination state&lt;/h3&gt;&lt;p&gt;The loading row also gives us a clear place to represent the current pagination state. For example, we can show a progress indicator while loading or display an error view with a retry option when the request fails.&lt;/p&gt;&lt;p&gt;This makes the behavior predictable and easy to extend.&lt;/p&gt;&lt;p&gt;The implementation of &lt;code&gt;loadMoreItems()&lt;/code&gt; should ensure that repeated &lt;code&gt;onAppear&lt;/code&gt; calls do not start multiple pagination requests before the previous one has finished.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Using a dedicated loading row is a simple and predictable way to implement pagination with SwiftUI&amp;#x27;s &lt;code&gt;List&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;By triggering the next page when this row becomes visible, we can build an infinite scrolling experience that is easy to understand and extend.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Building a draggable bottom sheet in SwiftUI]]></title><description><![CDATA[Learn how to build a draggable bottom sheet using presentation detents.]]></description><link>https://tanaschita.com/swiftui-draggable-bottom-sheet</link><guid isPermaLink="false">https://tanaschita.com/swiftui-draggable-bottom-sheet</guid><pubDate>Sun, 19 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Some apps, such as Apple Maps or Google Maps, use a draggable bottom sheet to let users switch between the main content, for example a map, and additional information presented in a sheet.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to build a draggable bottom sheet that works together with a scroll view and still allows interaction with the underlying content.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a3d29cb7c68ce190bc8650176635b491/634ab/example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:59.46666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAADnElEQVQozy2S+1NUdQDFv3+KPaiZioBd2GUfwA627C4Lq24bDuMWTSIWwSCCysXcBYIcHjsi3jLSTEIaU1JIg7ISUqMB5CGtV/JWKi6Phe3SepHHvXy/p8E8M2fmzOeXM2fmkIL9u0l++S6u1F+KQ/U+pSpQzWqaa1DsK5HfKdspHKirgL/RT6sC1dQX8OPA4QpRWlyU1tYUsCeCurS0hGg0yodCIUK8JXkkt8jLlVaV4VCjX/E1+llN84c4dpqX8/ftEvbXVcDXWEX9TVW0+kg1KusrxUVpQQJdA8AYGFUZpQCj/DqlhLxojScvpMdyMemxeD7tZeWZ5GdZjDkGFk+aHGvXCs9ZXkKM5RW6Sb+JbvCM3M3itzf/lb4aeITHq+tMoVDX1oE1Ch4AIclZJqJ3Gjldph7x6XGKfaeHGbalQGNPlPVOg5BkT0SiQ08zC3JonE2LbK9NbOqNSG39i3jw4BE70X5GLd1bjPJKji98dzch8RkaEpeRwGntWiTaNIrN62AWjwUml1lOdOqEDa536qkjz0lN2SY4c18Tr956LF3uu4uuwD7WUp6jflxbiqO+Qv7T2j2ExNk0G+Y0dg1StpgUc7aJWdwpSHEb5XS3VTC7UmF2magp20hTthqRsd0iBgc7pZ++/AjtRWkseKFJXZh5iImRUf6PYJCQeIeWxDk0XKz1VSQ5dIrFbWG6LB1inQnyua4OYXT0Om4FR+nYxAgdHh/CyO/DYn9vg9Rc+QbqC63sk8NFal5pAdzvufmCqiJCDFuSiGmrjkvI1CDBrlUMLjNLdhlhy02Tfx67J1y5A0RXQYEn3pD45+Sw9H3PGbQe97HTbc1qA/8BAq0H+eZTBwlx7bCR7B0ZXLJLj+Qso2LIMjDL66nw5Fvllku3hdqLEdwLrdL+wQn6+bnLOH9tUpwKzUlTD6cxPRNmodCMGlmQNor4lWWVkPeL80lh8U7uzYJtcOc5lc056czxlgPHTjXI3QP3hY5forj2zVkaKNtOPVmpyPN6xLnwrDQ7O4NweI7Nz4fV+YUFyEsyH4lECBm/O02Cf81w3w3dx2RIUlZWlpi8vAIGyFPCb8KvPR1o32OlffVe+s/fEwgO3RCX5FXp6XwGQH2a///hpdFlcvX2Csf/EMWNsbByR5xmP/Zfx5W+QXl4oE1ob3obgRI3/froXnrks1bUHW8Ruy/2SydPfIHeri524Xyn2tnVg5vjAn+yo5v8B4cIYAe37DqtAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Example of a draggable bottom sheet.&quot; title=&quot;Example of a draggable bottom sheet.&quot; src=&quot;/static/a3d29cb7c68ce190bc8650176635b491/2e9ed/example.png&quot; srcSet=&quot;/static/a3d29cb7c68ce190bc8650176635b491/12d18/example.png 375w,/static/a3d29cb7c68ce190bc8650176635b491/44507/example.png 750w,/static/a3d29cb7c68ce190bc8650176635b491/2e9ed/example.png 1500w,/static/a3d29cb7c68ce190bc8650176635b491/e90d0/example.png 2250w,/static/a3d29cb7c68ce190bc8650176635b491/9d489/example.png 3000w,/static/a3d29cb7c68ce190bc8650176635b491/634ab/example.png 4494w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Example of a draggable bottom sheet.&lt;/div&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Implementing the bottom sheet&lt;/h3&gt;&lt;p&gt;To be more precise, what we&amp;#x27;d like to achieve is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the sheet has multiple states such as collapsed, medium and expanded&lt;/li&gt;&lt;li&gt;the sheet is not fully dismissable by user interaction&lt;/li&gt;&lt;li&gt;the user can interact with the contents of the underlying content such as a map&lt;/li&gt;&lt;li&gt;dragging the sheet works together with a scroll view used to display its content&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can implement the described behaviour by using SwiftUI&amp;#x27;s &lt;code&gt;sheet&lt;/code&gt; combined with &lt;code&gt;presentationDetents&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var showingBottomSheet = true

    var body: some View {
        MapView()
            .sheet(isPresented: $showingBottomSheet) {
                ScrollView {
                    // Add content of the bottom sheet here.
                }
                .interactiveDismissDisabled()
                .presentationDetents([.height(50), .medium, .large])
                .presentationBackgroundInteraction(
                    .enabled(upThrough: .large)
                )
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s break this down.&lt;/p&gt;&lt;h3&gt;Understanding detents&lt;/h3&gt;&lt;p&gt;Detents define the different heights the sheet can snap to.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.presentationDetents([.height(50), .medium, .large])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.height(50)&lt;/code&gt; represents a collapsed state&lt;/li&gt;&lt;li&gt;&lt;code&gt;.medium&lt;/code&gt; is a partially expanded state&lt;/li&gt;&lt;li&gt;&lt;code&gt;.large&lt;/code&gt; expands the sheet to full height&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By combining these, we create a sheet that can move between multiple states depending on the user&amp;#x27;s interaction.&lt;/p&gt;&lt;h3&gt;Controlling dismissal behavior&lt;/h3&gt;&lt;p&gt;By default, sheets can be dismissed by dragging them down. In this case, we don&amp;#x27;t want that default behavior:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.interactiveDismissDisabled()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This ensures that the sheet stays visible and behaves more like a persistent UI element rather than a modal.&lt;/p&gt;&lt;h3&gt;Enabling background interaction&lt;/h3&gt;&lt;p&gt;One important aspect of the sheet is that the user can still interact with the underlying content, for example a map.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.presentationBackgroundInteraction(
    .enabled(upThrough: .large)
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This allows interaction with the content behind the sheet while it&amp;#x27;s not fully covering it.&lt;/p&gt;&lt;h3&gt;Working with scroll views&lt;/h3&gt;&lt;p&gt;To display content inside the sheet, we typically use a ScrollView.&lt;/p&gt;&lt;p&gt;SwiftUI automatically coordinates the scrolling behavior with the sheet’s dragging behavior:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;when the scroll view is scrolled, it handles the gesture&lt;/li&gt;&lt;li&gt;when it reaches the top, dragging continues to move the sheet&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This coordination is built into the system and does not require additional steps from our side.&lt;/p&gt;&lt;h3&gt;Adding a drag indicator&lt;/h3&gt;&lt;p&gt;To make the interaction more discoverable, we can show a drag indicator:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.presentationDragIndicator(.visible)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This provides a visual hint that the sheet can be dragged.&lt;/p&gt;&lt;h3&gt;Limitations&lt;/h3&gt;&lt;p&gt;One limitation of this approach is that the sheet is always presented above the entire view hierarchy.&lt;/p&gt;&lt;p&gt;This means it will also cover elements such as a tab bar. If more control over layering is required, for example to place content above the sheet, a custom implementation may be necessary.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to test in-app purchases locally using StoreKit]]></title><description><![CDATA[A practical guide to simulating in-app purchases and subscriptions in Xcode.]]></description><link>https://tanaschita.com/testing-in-app-purchases-locally-storekit</link><guid isPermaLink="false">https://tanaschita.com/testing-in-app-purchases-locally-storekit</guid><pubDate>Tue, 07 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with in-app purchases, testing different purchase scenarios is an important part of development. This includes handling successful transactions, failures, and subscription renewals.&lt;/p&gt;&lt;p&gt;StoreKit provides a local testing environment that allows us to simulate in-app purchases during development. This makes it possible to test purchase flows, subscriptions, and edge cases without connecting to App Store Connect.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to set up StoreKit testing and how to use it effectively during development.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Overview&lt;/h3&gt;&lt;p&gt;To test in-app purchases locally, we need to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Create and configure a StoreKit configuration file&lt;/li&gt;&lt;li&gt;Enable StoreKit testing in Xcode&lt;/li&gt;&lt;li&gt;Simulate purchases and manage transactions&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Before going through the setup, it helps to understand what the configuration file represents.&lt;/p&gt;&lt;h3&gt;Understanding StoreKit configuration files&lt;/h3&gt;&lt;p&gt;A StoreKit configuration file (&lt;code&gt;.storekit&lt;/code&gt;) defines the products and subscription behavior we want to simulate locally.&lt;/p&gt;&lt;p&gt;Instead of communicating with the App Store, StoreKit reads this configuration and returns simulated responses. This allows us to test purchase flows in a controlled and predictable environment.&lt;/p&gt;&lt;p&gt;Xcode supports two types of configuration files:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Local configuration&lt;/strong&gt;: Defined entirely in Xcode. Useful when setting up products for the first time or testing new scenarios.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Synced configuration&lt;/strong&gt;: Linked to App Store Connect. Reflects the products configured for an existing app.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The key difference is that local configurations are fully editable, while synced configurations mirror App Store Connect and cannot be modified directly.&lt;/p&gt;&lt;h3&gt;Creating a StoreKit configuration file&lt;/h3&gt;&lt;p&gt;To create a configuration file, Xcode provides a built-in template.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1446px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/16f36087b6b2e4a5295766e3a1ba6088/385e6/storekit-configuration-file-template.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:33.599999999999994%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABFUlEQVQoz5WRPU7DQBCF9xJUKQj2Oruetdc/iZ3YTuIkBFEgpIgCCgoKRBSJComT0NJTcgLK3Oshr43loBRQPL3Z0ZtvNFo2nOSIG4XJGPE4gz9MYEmC7SojTl5bH+uZmjzjLNy8oCwLFMtzFIsVsvkCk1mJJJ8iLWaI0omB82bAyCVYXgyxvMVodQUrykHZGm46BRNxjt6ZDakjCD/AwNOQfgCpQ7hBZLyCOMqHUznVzqu3HkEGMbjScLwAQvlgl9c3eHx+xakgcFUH7eaEnzOq3lGRMhmnmRFEYJv7Ld7eP9Ab6BbYlf1rwYG6OQNUYP3tHmr3hZO7T/R1Bk50EPyrWqC82EGun2DNH2B7Uf1j/wR1gd+7Ctk87tPZFwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;StoreKit configuration file&quot; title=&quot;StoreKit configuration file&quot; src=&quot;/static/16f36087b6b2e4a5295766e3a1ba6088/385e6/storekit-configuration-file-template.png&quot; srcSet=&quot;/static/16f36087b6b2e4a5295766e3a1ba6088/12d18/storekit-configuration-file-template.png 375w,/static/16f36087b6b2e4a5295766e3a1ba6088/44507/storekit-configuration-file-template.png 750w,/static/16f36087b6b2e4a5295766e3a1ba6088/385e6/storekit-configuration-file-template.png 1446w&quot; sizes=&quot;(max-width: 1446px) 100vw, 1446px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;StoreKit configuration file template.&lt;/div&gt;&lt;p&gt;When naming the configuration file, it can be helpful to use a descriptive name such as StoreKitConfigurationLocal.storekit, especially if multiple configurations are used.&lt;/p&gt;&lt;p&gt;After creating the file, we can start defining products.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Configuring a local StoreKit file&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In a local configuration file, we can manually add products and define their behavior.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/85ec2dfdcbeb46a259006ad07cda43c1/96fc5/storekit-configuration-file-local-configure.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:39.2%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABi0lEQVQoz1WSTW/TQBCG/SegEpDUcZz1Z2yv12snThw7TUmCUipRcShnDkgVZ/7+g3ZJDRwe6d3R7sy7M+NsuiOXh68cz18Y7i5U9UASZdSJT69zhjpn0DnrLECFsxEpbikD1+rylcDFmbQ/0I+/8PqfvGu+8759YVY/U4opVTyniuao0EMnPneN5L6tOHUN+6agzUOapWBlWaBCF0f7b+kyl1rcoP03FuXfUIhbisXUYiqbR5si5rBWXPYbjlvNfavs+bAu2bcNKglwZOhRiBky8DBahnN7Nkm0cRjPrV5nIbsqsy4fDp11ee5XnPs1n3YNH4celcY4xkHuT0ZeXZkeDnVBp9KxZya2ygK2MrFu/xLRyhQV+Th9teSwkrYn+zq3VLFH7k/HAsahSdikgm2Z2LumUKeWlp1K2VaSMhI4T6eBb48ny/PnI0+nnjYTNkmTLv6bqLy6//cnxagnSDHF0UVKLZfUMqMpc2QaErgf0PGftTFTVNeVMJOWgUsVeajIszHTY1v0qn8Dzmb6Jjsxg2IAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Locally configure in-app purchase options.&quot; title=&quot;Locally configure in-app purchase options.&quot; src=&quot;/static/85ec2dfdcbeb46a259006ad07cda43c1/2e9ed/storekit-configuration-file-local-configure.png&quot; srcSet=&quot;/static/85ec2dfdcbeb46a259006ad07cda43c1/12d18/storekit-configuration-file-local-configure.png 375w,/static/85ec2dfdcbeb46a259006ad07cda43c1/44507/storekit-configuration-file-local-configure.png 750w,/static/85ec2dfdcbeb46a259006ad07cda43c1/2e9ed/storekit-configuration-file-local-configure.png 1500w,/static/85ec2dfdcbeb46a259006ad07cda43c1/96fc5/storekit-configuration-file-local-configure.png 1882w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Locally configure in-app purchase options.&lt;/div&gt;&lt;p&gt;Using the &lt;code&gt;+&lt;/code&gt; button, we can add consumables, non-consumables, auto-renewable subscriptions and non-renewing subscriptions.&lt;/p&gt;&lt;p&gt;For each product, we can define the product identifier, pricing and subscription duration if applicable.&lt;/p&gt;&lt;p&gt;This allows us to simulate realistic purchase scenarios.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Configuring a synced StoreKit file&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;If products are already set up in App Store Connect, we can create a synced configuration file.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1438px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/58bae62196ac5c2b79f19ca323408321/7073c/storekit-synced-configuration-file.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:71.46666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAABOklEQVQ4y52T61KDMBCFeRagXHJbQqBlBHSorVV0xmrf/1mOk9R2qNAq/vjIj2zOnr3gUbnCsm5R1q07Y6EQpBwhE7M4vfEioRBLOjO8nCtmT89+fuIuZwhdOBwHDtzZoBEMgYNPVuNds+7gCj4n+Ewd4YRAZAgtXE62ZuTQTzkYZdB1h2J3QNvv0b5+on7Zo+k/oB+eEJgaoa6c25uC54xJCl+tUGwOeOzf0PXvaDbPoKpBRAYLZRAK+qPDhCEl7Vao7dZYb3eomntIXYBMCTJL5GUFoY2r5ldBm3XBJURegGU5uDZIKUcss28IicwQXdnXq0PxJ9Zpcr1uTfk/Sz1y6Nwk7GbWYbKLKgbvTni2X6nKZv12NiYW5IZn31siLo8ObePb9QblXTNL0FQ1uu0OMi+dsDKlS/IFtTmka52SPQwAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Using synced configuration files.&quot; title=&quot;Using synced configuration files.&quot; src=&quot;/static/58bae62196ac5c2b79f19ca323408321/7073c/storekit-synced-configuration-file.png&quot; srcSet=&quot;/static/58bae62196ac5c2b79f19ca323408321/12d18/storekit-synced-configuration-file.png 375w,/static/58bae62196ac5c2b79f19ca323408321/44507/storekit-synced-configuration-file.png 750w,/static/58bae62196ac5c2b79f19ca323408321/7073c/storekit-synced-configuration-file.png 1438w&quot; sizes=&quot;(max-width: 1438px) 100vw, 1438px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Adding a synced StoreKit configuration file.&lt;/div&gt;&lt;p&gt;During creation, we select the app and team to sync with. Xcode then mirrors the existing configuration.&lt;/p&gt;&lt;p&gt;Synced files are useful for testing production setups, but they cannot be edited directly. If needed, we can convert them into local configurations.&lt;/p&gt;&lt;h3&gt;Enabling StoreKit testing in Xcode&lt;/h3&gt;&lt;p&gt;Once the configuration file is ready, we need to attach it to our app.&lt;/p&gt;&lt;p&gt;StoreKit testing is enabled through the current scheme. In the scheme editor, we select the configuration file:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/dbd3431852844bcf26b73b8fa686e18b/fde7f/enable-storekit-configuration-file.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:51.46666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB/klEQVQoz1WSa4pTQRCFswNxB05yc5Pbj+q+fR+5z7wmozDqiIgj/pQBfwgyDLgAFyC4EHEBru6TdAKOPw7VUKdP1amqybO7n2Tf//D0/jdPvv4irTcorcmsQ4lDQkCqllA3VG1HuXmBlA2uKFgaYWmFzJ6i8TkTH0ouDweU0SyWS+baMlOWqTJcZBoTSsJwSbt7TrPe0aw36FChQ43ULVleMM105KtQMsnrlus3b5GyYq4MqRFmyrAQj/IBU9QUq5pQBYwzGG+xXpBc8EFQzkX+ETovmNhQ0o6bmEi1ZW4kVls6z7A/MO731LtXtNd35PuPuO0H1PCe2eods+aWxI8kSv0TTLWQZJpUGxbGni2fKtpyRVitkP4l1c036psHytcP5Nf3zDafudh8IQkHkixjqiz6aHlhhBMeCZ5FXd2wPlxR9WuavmXYDHRDS7/uEG+ZzS+iWHLmZz6cBFNj/+swEjKNygvyVYOpR/yqp+pHym7EH5fhC+bGxRElysQ/R0wWWuLsHgtOz5ZNURHaHilrVsPIsNsz7vZIKOMCM3EsrDvzNYk2jy3biGW8KUcmHldW9JsdzbiOYuvLK8q2Q4oKW5Qxr30eL2MhgYXkTFJlmStNqnSM1nl22y2Hw4G2bem6Lr7HoSdvtjTdiIhFRBBxOKvRxcD89gfp/hN/ARe1PqYUX087AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Enabling StoreKit testing in Xcode.&quot; title=&quot;Enabling StoreKit testing in Xcode.&quot; src=&quot;/static/dbd3431852844bcf26b73b8fa686e18b/2e9ed/enable-storekit-configuration-file.png&quot; srcSet=&quot;/static/dbd3431852844bcf26b73b8fa686e18b/12d18/enable-storekit-configuration-file.png 375w,/static/dbd3431852844bcf26b73b8fa686e18b/44507/enable-storekit-configuration-file.png 750w,/static/dbd3431852844bcf26b73b8fa686e18b/2e9ed/enable-storekit-configuration-file.png 1500w,/static/dbd3431852844bcf26b73b8fa686e18b/fde7f/enable-storekit-configuration-file.png 1862w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Enabling StoreKit testing in Xcode.&lt;/div&gt;&lt;p&gt;From this point on, all StoreKit requests are resolved using the selected configuration file instead of the App Store.&lt;/p&gt;&lt;p&gt;If we want to disable local testing, we can set the configuration back to &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Testing purchase flows&lt;/h3&gt;&lt;p&gt;With StoreKit testing enabled, we can trigger purchases directly in the app.&lt;/p&gt;&lt;p&gt;From our code’s perspective, nothing changes. StoreKit behaves as if it were communicating with the App Store, but responses are generated locally.&lt;/p&gt;&lt;p&gt;This allows us to test successful purchases, failed transactions, subscription renewals, expired subscriptions and more. Because everything runs locally, tests are fast and repeatable.&lt;/p&gt;&lt;h3&gt;Managing transactions&lt;/h3&gt;&lt;p&gt;Xcode provides tools to inspect and control transactions during testing.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/eb9df4ce46acf3322ae0218564684ff8/9eec3/storekit-manage-transactions.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:46.4%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABQUlEQVQoz42P626bQBBG8xBtsLnsDnsFAjhgh1hOo/YR+v4vcyqWOqobueqPo087Gp395mEKwhw0cxSmoIk6x5QZdZHxJAVvQ+QlGiZbMTuV8ug1lz5wGVtGbzDFDq8KBi88/EvYJWHD0twKT0F4G5skPASLq/L/E64N1yZrw2dTJuHK2nBt/j71HKLFqzvC+ZOw5NxaLn1kae2HcHY65WvnGVz9+eS12fS7YfhD2OqCUzAsnecYDAdTMUjJ0jp+vEwMrz9pfIctM7wqN+EchOMHmkbnaeEq1fuvqN2XlJI/buy3NHmGLfe3Jy9R+BaFSxTeo3CSAlftsDfs/3pvs6voShKOQeid5slpnp3mLBWdrnCqwKj8LumTItsod2l/DPUqrBmcTvSh5mQV363m6DStVURRBF0R5D5eKsZoOY+BX3JR/xQTjB+1AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s transaction manager&quot; title=&quot;Xcode&amp;#x27;s transaction manager&quot; src=&quot;/static/eb9df4ce46acf3322ae0218564684ff8/2e9ed/storekit-manage-transactions.png&quot; srcSet=&quot;/static/eb9df4ce46acf3322ae0218564684ff8/12d18/storekit-manage-transactions.png 375w,/static/eb9df4ce46acf3322ae0218564684ff8/44507/storekit-manage-transactions.png 750w,/static/eb9df4ce46acf3322ae0218564684ff8/2e9ed/storekit-manage-transactions.png 1500w,/static/eb9df4ce46acf3322ae0218564684ff8/9eec3/storekit-manage-transactions.png 2158w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s transaction manager&lt;/div&gt;&lt;p&gt;We can open the transaction manager from the bottom toolbar when the configuration file is open or via &lt;code&gt;Debug → StoreKit → Manage Transactions&lt;/code&gt; in Xcode&amp;#x27;s menu.&lt;/p&gt;&lt;p&gt;From here, we can reset purchases, simulate refunds, test subscription renewals or cancellations. This makes it possible to reproduce edge cases that are difficult to test using the App Store sandbox.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;StoreKit testing allows us to simulate in-app purchases directly in Xcode without relying on external systems.&lt;/p&gt;&lt;p&gt;By defining products in a configuration file and attaching it to our app, we can test purchase flows quickly, reproduce edge cases, and significantly reduce the feedback loop during development.&lt;/p&gt;&lt;p&gt;Once this setup is in place, working with in-app purchases becomes fast and predictable. Before releasing an app, it is still important to test against the App Store sandbox to validate real-world behavior.&lt;/p&gt;</content:encoded><category>storekit</category><category>testing</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Working with files and directories in iOS]]></title><description><![CDATA[Understanding the app sandbox and how to access system directories.]]></description><link>https://tanaschita.com/ios-file-system-overview</link><guid isPermaLink="false">https://tanaschita.com/ios-file-system-overview</guid><pubDate>Sun, 05 Apr 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developing an iOS app, we often need to persist data that doesn&amp;#x27;t naturally fit into a database or &lt;code&gt;UserDefaults&lt;/code&gt;. This can include images, exported files, cached responses, or other user-generated content.&lt;/p&gt;&lt;p&gt;iOS provides a well-defined structure for storing files inside an app’s sandbox. Understanding how these directories work, and how to interact with them, allows us to build predictable and maintainable file handling into our apps.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Understanding the app sandbox&lt;/h3&gt;&lt;p&gt;Each iOS app operates within its own sandbox directory. For security reasons, our app can only access files inside this container, unless explicit permission is granted (for example when accessing photos or documents).&lt;/p&gt;&lt;p&gt;The sandbox is divided into two main areas:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Bundle container&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Contains the app bundle (.app) with all bundled resources such as images, localization files, or JSON files. This directory is read-only at runtime.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Data container&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Contains all files created and managed by the app. This is where we read and write data during runtime.&lt;/p&gt;&lt;p&gt;Inside the data container, there are three important directories:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Documents&lt;/code&gt;: Used for user-generated content.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Library&lt;/code&gt;: Used for app-specific data that should not be directly exposed to the user.&lt;/li&gt;&lt;li&gt;&lt;code&gt;tmp&lt;/code&gt;: Used for temporary files that can be deleted by the system at any time.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s take a closer look at the most relevant directories.&lt;/p&gt;&lt;h3&gt;The Documents directory&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;Documents&lt;/code&gt; directory is intended for user-generated content. This includes anything a user might create, edit, or explicitly interact with, such as images, videos, or exported files.&lt;/p&gt;&lt;p&gt;We can freely create subdirectories here to organize content.&lt;/p&gt;&lt;p&gt;The system also provides a &lt;code&gt;Documents/Inbox&lt;/code&gt; directory, which is used when other apps hand over files to our app. Files in this directory can be read and deleted, but not modified in place.&lt;/p&gt;&lt;h3&gt;The Library directory&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;Library&lt;/code&gt; directory contains data used internally by the app. The most relevant subdirectories are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Library/Application Support&lt;/code&gt;: Used for files the app needs to function, such as configuration files or stored data that should not be visible to the user.&lt;/li&gt;&lt;li&gt;&lt;code&gt;Library/Caches&lt;/code&gt;: Used for cached data that can be recreated if needed. The system may remove these files to free up disk space.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Accessing directories&lt;/h3&gt;&lt;p&gt;So far, we’ve looked at where files live inside the app sandbox. The next step is accessing these locations in code.&lt;/p&gt;&lt;p&gt;In iOS, file locations are represented by &lt;code&gt;URL&lt;/code&gt;. Instead of working with string paths, we use these URLs as references to files and directories. We can access common directories directly through &lt;code&gt;URL&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let documentsURL = URL.documentsDirectory
let cachesURL = URL.cachesDirectory
let applicationSupportURL = URL.applicationSupportDirectory
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These URLs point to the corresponding locations inside our app&amp;#x27;s data container. Under the hood, these values are resolved using &lt;code&gt;FileManager&lt;/code&gt;. If we need more control, we can fall back to:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let directoryURL = try FileManager.default.url(
    for: .applicationSupportDirectory,
    in: .userDomainMask,
    appropriateFor: nil,
    create: false
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In most cases, however, the &lt;code&gt;URL&lt;/code&gt; APIs are sufficient and keep the code more concise.&lt;/p&gt;&lt;h3&gt;Reading and writing files&lt;/h3&gt;&lt;p&gt;Once we have a directory &lt;code&gt;URL&lt;/code&gt;, we can create a file &lt;code&gt;URL&lt;/code&gt; by appending a file name, and then use it to read or write data.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let fileURL = directoryURL.appendingPathComponent(&amp;quot;fileName.json&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To write data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;try data.write(to: fileURL)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To read data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let data = try Data(contentsOf: fileURL)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Creating directories&lt;/h3&gt;&lt;p&gt;We can create custom subdirectories to organize files:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let customDirectoryURL = directoryURL.appendingPathComponent(&amp;quot;directoryName&amp;quot;)

try FileManager.default.createDirectory(
    at: customDirectoryURL,
    withIntermediateDirectories: false,
    attributes: nil
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the directory already exists, this call will throw an error. In practice, we either check beforehand using &lt;code&gt;fileExists(atPath:)&lt;/code&gt; or handle the error accordingly.&lt;/p&gt;&lt;h3&gt;Accessing bundled resources&lt;/h3&gt;&lt;p&gt;So far, we&amp;#x27;ve only looked at files that are created and managed by our app at runtime.&lt;/p&gt;&lt;p&gt;In addition to that, we often need to read files that are shipped with the app, such as configuration files or sample data. These files live in the app bundle, which is part of the read-only bundle container.&lt;/p&gt;&lt;p&gt;To access them, we can use the &lt;code&gt;Bundle&lt;/code&gt; type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let url = Bundle.main.url(forResource: &amp;quot;nameOfTheResource&amp;quot;, withExtension: &amp;quot;json&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once we have the file URL, we can read it just like any other file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let data = try Data(contentsOf: url)
let configuration = try jsonDecoder.decode(Configuration.self, from: data)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The key difference is that bundled resources are read-only, while files in the data container can be both read and written.&lt;/p&gt;&lt;h3&gt;Managing file backups&lt;/h3&gt;&lt;p&gt;By default, files stored in the &lt;code&gt;Documents&lt;/code&gt; directory and in &lt;code&gt;Library/Application Support&lt;/code&gt; are included in iCloud backups.&lt;/p&gt;&lt;p&gt;In some cases, this is not what we want. For example, cached or downloadable data can be recreated and does not need to be backed up.&lt;/p&gt;&lt;p&gt;To control this behavior, we can attach metadata to a file URL.&lt;/p&gt;&lt;p&gt;In iOS, file URLs can store additional resource values that describe how the system should treat a file. One of these values determines whether a file is included in backups.&lt;/p&gt;&lt;p&gt;We can update this value using URLResourceValues:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try url.setResourceValues(resourceValues)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By setting &lt;code&gt;isExcludedFromBackup&lt;/code&gt; to true, we tell the system that the file should not be included in iCloud backups.&lt;/p&gt;&lt;p&gt;This is especially useful for files in the Caches directory or any data that can be recreated if needed.&lt;/p&gt;</content:encoded><category>file system</category><category>persistence</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Changing runtime state with LLDB while debugging iOS apps]]></title><description><![CDATA[Learn how to use LLDB's expr command to modify variables at runtime.]]></description><link>https://tanaschita.com/lldb-changing-variables-while-debugging</link><guid isPermaLink="false">https://tanaschita.com/lldb-changing-variables-while-debugging</guid><pubDate>Sun, 29 Mar 2026 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developing an iOS app, we often need to bring the app into a very specific state, for example when reproducing bugs or testing edge cases.&lt;/p&gt;&lt;p&gt;LLDB provides a powerful way to achieve that: we can modify values at runtime using the expression command, without touching our source code or restarting the app.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s take a look at how this works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Modifying values from the console&lt;/h3&gt;&lt;p&gt;When our app pauses at a breakpoint, we can use Xcode&amp;#x27;s console to change a variable to a state we need.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s imagine, we are trying to fix a bug that only is reproducable for birds that have a &lt;code&gt;critically endangered&lt;/code&gt; conservation state. When a breakpoint hits the relevant line, let&amp;#x27;s at first print the bird&amp;#x27;s &lt;code&gt;conservationStatus&lt;/code&gt; in Xcode&amp;#x27;s console:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(lldb) po bird.conservationStatus.title
&amp;quot;endangered&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we&amp;#x27;d like to test the app with a different &lt;code&gt;conservationStatus&lt;/code&gt;, we can use the &lt;code&gt;expr&lt;/code&gt; (or its shorthand &lt;code&gt;p&lt;/code&gt;) command to change it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(lldb) p bird.conservationStatus = .cr
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When printing the &lt;code&gt;conservationStatus&lt;/code&gt; again, we get:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(lldb) po bird.conservationStatus.title
&amp;quot;critically endangered&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From here, we can continue execution and observe how the app behaves under this modified state.&lt;/p&gt;&lt;p&gt;This is especially useful when a bug only appears under rare conditions that are otherwise difficult to reproduce.&lt;/p&gt;&lt;h3&gt;When this approach is useful&lt;/h3&gt;&lt;p&gt;In practice, this is not the only way to approach these situations. In many cases, we might simply hardcode a value directly in the code to reach the desired state and avoid committing that change. This approach is often more explicit and easier to reason about, especially when working with Swift&amp;#x27;s type system.&lt;/p&gt;&lt;p&gt;Modifying state with LLDB becomes useful in situations where reproducing a specific state is otherwise time-consuming or difficult.&lt;/p&gt;&lt;p&gt;For example, we might already be several screens deep in a navigation flow and want to test a specific edge case. Instead of restarting the app and navigating back to the same point, we can modify the state directly at the breakpoint:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;expr result = .error
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach is also helpful when experimenting with different values during debugging. Instead of repeatedly changing code and rebuilding, we can quickly explore different scenarios:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;expr config.isEnabled = false
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Limitations&lt;/h3&gt;&lt;p&gt;While this technique is powerful, there are a few important limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Constants (let) cannot be modified: attempting to assign a new value will result in an error.&lt;/li&gt;&lt;li&gt;Value types may behave unexpectedly: when working with structs, we are mutating a copy in some contexts. The change may not propagate as expected.&lt;/li&gt;&lt;li&gt;SwiftUI state isn&amp;#x27;t always mutable this way: properties like &lt;code&gt;@State&lt;/code&gt; may not respond reliably to LLDB mutations.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Automating changes with breakpoint actions&lt;/h3&gt;&lt;p&gt;When reproducing a bug, we sometimes want a variable to always have a certain value without typing the command every time in the console.&lt;/p&gt;&lt;p&gt;In this case, we can use a breakpoint action.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Set a breakpoint at the relevant location&lt;/li&gt;&lt;li&gt;Right-click the breakpoint and choose Edit Breakpoint&lt;/li&gt;&lt;li&gt;Add an action of type &lt;code&gt;Debugger Command&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Enter the command:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;expr bird.conservationStatus = .cr
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;5&quot;&gt;&lt;li&gt;Enable &lt;code&gt;Automatically continue&lt;/code&gt; after evaluating actions.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Now, every time the breakpoint is hit, LLDB applies the change and continues execution immediately. This allows us to consistently reproduce a specific scenario without interrupting the flow.&lt;/p&gt;</content:encoded><category>lldb</category><category>testing</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Migrating to String Catalogs in iOS]]></title><description><![CDATA[Learn about migration strategies from Localizable.strings to .xcstrings.]]></description><link>https://tanaschita.com/ios-string-catalogs-migration</link><guid isPermaLink="false">https://tanaschita.com/ios-string-catalogs-migration</guid><pubDate>Sun, 22 Mar 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many existing iOS projects still rely on &lt;code&gt;Localizable.strings&lt;/code&gt; and &lt;code&gt;Localizable.stringsdict&lt;/code&gt; files for localization. String Catalogs provide a more integrated workflow by allowing Xcode to manage localization keys automatically.&lt;/p&gt;&lt;p&gt;In this quick tip, we&amp;#x27;ll look at how to migrate an existing project to String Catalogs.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Why migrate to String Catalogs?&lt;/h3&gt;&lt;p&gt;Before migrating, it&amp;#x27;s worth understanding what actually improves.&lt;/p&gt;&lt;p&gt;With traditional &lt;code&gt;.strings&lt;/code&gt; files:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;keys are added manually&lt;/li&gt;&lt;li&gt;unused keys can accumulate over time&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;String Catalogs improve this by:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;automatically detecting localization keys in code&lt;/li&gt;&lt;li&gt;providing a visual editor in Xcode&lt;/li&gt;&lt;li&gt;tracking states such as &lt;code&gt;new&lt;/code&gt;, &lt;code&gt;stale&lt;/code&gt;, and &lt;code&gt;needs_review&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For many projects, this reduces maintenance effort.&lt;/p&gt;&lt;p&gt;If a project uses third-party localization tools or services, it&amp;#x27;s worth verifying that they support String Catalogs before migrating.&lt;/p&gt;&lt;h3&gt;Backward compatibility&lt;/h3&gt;&lt;p&gt;When considering a migration, backward compatibility is often a concern.&lt;/p&gt;&lt;p&gt;String Catalogs are fully backward compatible. During the build process, Xcode converts the catalog back into &lt;code&gt;.strings&lt;/code&gt; and &lt;code&gt;.stringsdict&lt;/code&gt; files. This means existing APIs and runtime behavior remain unchanged.&lt;/p&gt;&lt;h3&gt;Xcode&amp;#x27;s automatic migration&lt;/h3&gt;&lt;p&gt;Xcode provides built-in support for migrating existing localization files. To migrate, simply right-click on the &lt;code&gt;Localizable.strings&lt;/code&gt; file and select &lt;code&gt;Migrate to String Catalog&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Xcode will:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;move existing keys into a .xcstrings file&lt;/li&gt;&lt;li&gt;preserve translations for all languages&lt;/li&gt;&lt;li&gt;convert pluralization rules automatically&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Migrating over time&lt;/h3&gt;&lt;p&gt;Migrating incrementally is possible and can work well when introducing clear boundaries.&lt;/p&gt;&lt;p&gt;Instead of converting the default &lt;code&gt;Localizable.strings&lt;/code&gt; setup all at once, a practical approach is to create additional String Catalogs for new features. For example, we might create a catalog such as &lt;code&gt;Checkout.xcstrings&lt;/code&gt; (instead of &lt;code&gt;Localizable.xcstrings&lt;/code&gt;) and explicitly use it in code.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;checkout.title&amp;quot;, tableName: &amp;quot;Checkout&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;existing keys remain in &lt;code&gt;Localizable.strings&lt;/code&gt;&lt;/li&gt;&lt;li&gt;new keys are added to &lt;code&gt;Checkout.xcstrings&lt;/code&gt;&lt;/li&gt;&lt;li&gt;both systems can coexist without duplicating keys&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The important detail is that keys are only added to a specific String Catalog when that table is explicitly used in code. This makes it possible to migrate feature by feature over time.&lt;/p&gt;</content:encoded><category>localization</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Working with String Catalogs in iOS projects]]></title><description><![CDATA[Practical tips, limitations, and best practices for localization with .xcstrings files.]]></description><link>https://tanaschita.com/ios-string-catalogs-in-practice</link><guid isPermaLink="false">https://tanaschita.com/ios-string-catalogs-in-practice</guid><pubDate>Sun, 15 Mar 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Localization is an essential part of many iOS apps. Prior to String Catalogs, developers managed localized strings using &lt;code&gt;Localizable.strings&lt;/code&gt; and &lt;code&gt;Localizable.stringsdict&lt;/code&gt; files. While this approach works well, it requires manual maintenance and can easily lead to missing or outdated localization keys.&lt;/p&gt;&lt;p&gt;String Catalogs simplify this workflow by integrating localization management directly into Xcode. In this article, we&amp;#x27;ll look at how String Catalogs work in practice. Let&amp;#x27;s jump right in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is a String Catalog?&lt;/h3&gt;&lt;p&gt;A String Catalog is a file with the extension &lt;code&gt;.xcstrings&lt;/code&gt;, typically named &lt;code&gt;Localizable.xcstrings&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Contrary to &lt;code&gt;Localizable.strings&lt;/code&gt; and &lt;code&gt;Localizable.stringdict&lt;/code&gt; files, a String Catalog manages all translations in a single structured file. Inside Xcode, the catalog provides a dedicated editor where we can view and manage all localized strings across supported languages.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/532cb71db1d799819cbf7a4db1f64f78/9240c/string-catalog.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:18.933333333333337%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAtUlEQVQY002OWQ7CMAxEexO6kKTZm6U7Qgjuf6ZBtkTFx9M4Gc/ITR9X+LJAhQSbKnwuKOcTeTtxE4pp5YhO6WvulUYnR7SCZgNpHCOMRfN6f+BThg4R0gZeIGjZ5xllPViF8Yh1hZ0K+zokxLLgrh3m7YCNE7QLaKiElom6nyjbyUF6T/OGuj8w+omD5Of1gHKRfdJhtOwN1mEwDs3vIsLnel1AUBFpJw0HXapc9J+hf1ZtmS9R63iuc046tAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;String catalog&quot; title=&quot;String catalog&quot; src=&quot;/static/532cb71db1d799819cbf7a4db1f64f78/2e9ed/string-catalog.png&quot; srcSet=&quot;/static/532cb71db1d799819cbf7a4db1f64f78/12d18/string-catalog.png 375w,/static/532cb71db1d799819cbf7a4db1f64f78/44507/string-catalog.png 750w,/static/532cb71db1d799819cbf7a4db1f64f78/2e9ed/string-catalog.png 1500w,/static/532cb71db1d799819cbf7a4db1f64f78/9240c/string-catalog.png 2056w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;Internally, a String Catalog is stored as JSON. A simplified example might look like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;sourceLanguage&amp;quot;: &amp;quot;en&amp;quot;,
  &amp;quot;strings&amp;quot;: {
    &amp;quot;loginButton.title&amp;quot;: {
      &amp;quot;localizations&amp;quot;: {
        &amp;quot;de&amp;quot;: {
          &amp;quot;stringUnit&amp;quot;: {
            &amp;quot;state&amp;quot;: &amp;quot;translated&amp;quot;,
            &amp;quot;value&amp;quot;: &amp;quot;Einloggen&amp;quot;
          }
        },
        &amp;quot;en&amp;quot;: {
          &amp;quot;stringUnit&amp;quot;: {
            &amp;quot;state&amp;quot;: &amp;quot;translated&amp;quot;,
            &amp;quot;value&amp;quot;: &amp;quot;Login&amp;quot;
          }
        }
      }
    }
  },
  &amp;quot;version&amp;quot;: &amp;quot;1.0&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Automatic detection of localizable strings&lt;/h3&gt;&lt;p&gt;One of the most convenient features of String Catalogs is Xcode&amp;#x27;s automatic detection of localized strings.&lt;/p&gt;&lt;p&gt;Whenever the project is built, Xcode scans the codebase for known localization APIs and automatically adds detected keys to the String Catalog.&lt;/p&gt;&lt;p&gt;For example, when using SwiftUI&amp;#x27;s &lt;code&gt;Text&lt;/code&gt; view with &lt;code&gt;Text(&amp;quot;loginButton.title&amp;quot;)&lt;/code&gt;, Xcode automatically creates a new entry for us.&lt;/p&gt;&lt;p&gt;Once the entry is created, translations can be added directly in the catalog editor.&lt;/p&gt;&lt;p&gt;For cases where localized strings need to be passed around in code, the &lt;code&gt;LocalizedStringResource&lt;/code&gt; type can be used to maintain automatic detection.&lt;/p&gt;&lt;h3&gt;Manual management of localized strings&lt;/h3&gt;&lt;p&gt;In some cases, Xcode cannot detect keys, for example when they are constructed dynamically:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;loginButton.&amp;quot; + state)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since Xcode cannot determine the final key during compilation, these strings will not appear automatically in the String Catalog. In such cases, entries must be added manually.&lt;/p&gt;&lt;p&gt;When we switch an entry to manual management, Xcode will no longer modify or remove it automatically.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ce82f97ed6fb953d2dd639cb7f3a61ad/7bf72/manual-management.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:22.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA7ElEQVQY01WPTUvDQBRF8wfEnVhstdU2mUkyM5mPTNJasdRiRRHsWhR0J/j/t0cmiODi8N59XB73ZkI3FMogtUEaS1iuaXzAuEClG0SlkLVC1Ioy+X73oqpZVIpFqZgXBWfrD0abb7L0bJaLP64Kiag1LnYo61nIirysh+lij3aB88s52lq6zmF0yWw6ZnRyxPj0mOx2e8f+8Ynt7p7l9Q02tDShJd0ToesHbUPEtpHGt+RS4lcb/O6dvDswaZ4Z2xcmek/mY0+7XA1GbT3aeYzzQ8JUO+l/pNRCUJiIf/ii3HxyEV+Z9m/MwoEfCUJ94xyEGwUAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Manage String Catalog entries manually&quot; title=&quot;Manage String Catalog entries manually&quot; src=&quot;/static/ce82f97ed6fb953d2dd639cb7f3a61ad/2e9ed/manual-management.png&quot; srcSet=&quot;/static/ce82f97ed6fb953d2dd639cb7f3a61ad/12d18/manual-management.png 375w,/static/ce82f97ed6fb953d2dd639cb7f3a61ad/44507/manual-management.png 750w,/static/ce82f97ed6fb953d2dd639cb7f3a61ad/2e9ed/manual-management.png 1500w,/static/ce82f97ed6fb953d2dd639cb7f3a61ad/7bf72/manual-management.png 1884w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Managing String Catalog entries manually&lt;/div&gt;&lt;p&gt;Manual management can be useful when:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;localization keys are generated dynamically&lt;/li&gt;&lt;li&gt;strings are shared across multiple modules&lt;/li&gt;&lt;li&gt;keys are intentionally kept even if not currently referenced in code&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Understanding String Catalog states&lt;/h3&gt;&lt;p&gt;String Catalog entries can appear in different states depending on how they are used in the project. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;new&lt;/code&gt; - the string was detected but has not yet been translated.&lt;/li&gt;&lt;li&gt;&lt;code&gt;needs_review&lt;/code&gt; - indicates that a translation should be checked again.&lt;/li&gt;&lt;li&gt;&lt;code&gt;stale&lt;/code&gt; - the string is no longer referenced in the code.&lt;/li&gt;&lt;li&gt;&lt;code&gt;translated&lt;/code&gt; - all required translations are available.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When Xcode marks an entry as &lt;code&gt;stale&lt;/code&gt;, it means the key is no longer detected in the codebase.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/186710fa2d0a888b67ddbf2622f3175c/7385a/stale-state.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:16%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAArUlEQVQI1zWO3WqEMBhEfRNd3W40X75EE/xpdm2hsN6U9qbv/yynmNKL4TAMHKY6vn84Pr9Iy4qbEp14aiPUxnEZtLCzHuNHOusKbxq4imI00PTnrqWfqWJ+J+W9lMYIed94HpnHPrFujrdd+HjeGdcH3SCMyysuzlzFI3FmzRN29LTDn7iqX3oaY2mto+mFkAL5Hlm2QJodOSvT/P/Eceml5BSc1KjcnNIOWoS/8ThcZaGzS9IAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Stale state&quot; title=&quot;Stale state&quot; src=&quot;/static/186710fa2d0a888b67ddbf2622f3175c/2e9ed/stale-state.png&quot; srcSet=&quot;/static/186710fa2d0a888b67ddbf2622f3175c/12d18/stale-state.png 375w,/static/186710fa2d0a888b67ddbf2622f3175c/44507/stale-state.png 750w,/static/186710fa2d0a888b67ddbf2622f3175c/2e9ed/stale-state.png 1500w,/static/186710fa2d0a888b67ddbf2622f3175c/7385a/stale-state.png 1800w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Stale state&lt;/div&gt;&lt;p&gt;At this point, the entry can either be removed or switched to manual management.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;needs_review&lt;/code&gt; state can be set manually to indicate that a translation should be checked again, for example when the source text changes:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/189d80a621d3eb4776c12f71709d2ac5/9c078/mark-for-review.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:14.399999999999999%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAw0lEQVQI1yXGy06DQBiAUZ7CpG6En6GFuTDMDFBaL22JduXC2EQ3GjUufAHf/zOpi5OcbH365WL6QOKeylgK7SibBqUbVtZSO0tlNJX1LF1HaVqUaal9RFl/fhgnujRgfCCT6ZnL/onc39JtZ9x6hwyP6P0b4fiFmT+R6US4eSDdHclNQtxIuzmg/IS4gTpdU7ge8SOZrCpUvURqQ+l6lEuozQvu/gc9f7MY3lmkV0rbIzZSmHB21XQU+v+5DoiJFDbyB5LpXi9opZQbAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Mark for review&quot; title=&quot;Mark for review&quot; src=&quot;/static/189d80a621d3eb4776c12f71709d2ac5/2e9ed/mark-for-review.png&quot; srcSet=&quot;/static/189d80a621d3eb4776c12f71709d2ac5/12d18/mark-for-review.png 375w,/static/189d80a621d3eb4776c12f71709d2ac5/44507/mark-for-review.png 750w,/static/189d80a621d3eb4776c12f71709d2ac5/2e9ed/mark-for-review.png 1500w,/static/189d80a621d3eb4776c12f71709d2ac5/9c078/mark-for-review.png 2038w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Mark a string for review&lt;/div&gt;&lt;h3&gt;Practical tip: renaming localization keys&lt;/h3&gt;&lt;p&gt;When working with localization keys, it&amp;#x27;s common that naming conventions evolve over time. For example, we might decide to rename a key like &lt;code&gt;loginButton.title&lt;/code&gt; to something more structured such as &lt;code&gt;login.button.title&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;A straightforward approach would be to simply update the key in the codebase:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;login.button.title&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, when doing this, Xcode treats the new key as a completely new entry. The old key will be marked as &lt;code&gt;stale&lt;/code&gt;, while the new key appears as &lt;code&gt;new&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This means all translations associated with the old key would need to be copied manually to the new one.&lt;/p&gt;&lt;p&gt;A more practical approach is to rename the key directly in the String Catalog editor in Xcode. When renaming a key from within the catalog, Xcode updates the references in the codebase automatically while keeping the existing translations intact:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/89e8f06462a08babffc4d5d29add7030/5ddad/change-key.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:37.06666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABH0lEQVQoz32OS07DMBRFsxNqNxLEv+brxGkSp0lTWkDtDIkBy2DAChgwYsUXxelHqgqDo+enZ517PT+uoVdPSO0OSbNFZndQpgfTLYK0BlU5iNQgaiTHfVLBDw1mctqv8QhTuAsWWBQVTDugXG1Q2AGRsWBxjrmMQXgEKiIQEWHGQhAenvdrPMo4RvKmhR22DmM7ZFUDlRXwuXT3ORMTXFzeN/AoE6BMIspLZMsGuraQaY4gTNz0uQJ1InmG/oMTjuZQG5i2R9n2yKpJKhINXygXOEKO00mvQk4cGwos+w0Or29YvxxQdgNy20E3HcL9J8r3Hzzsv0Cev0HXH6BMuRL0L+EpkQTcMWfy2EwgLmosV4+ouq0jLe3054ZsFP4Cg9Ta6d4+uIwAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Change key&quot; title=&quot;Change key&quot; src=&quot;/static/89e8f06462a08babffc4d5d29add7030/2e9ed/change-key.png&quot; srcSet=&quot;/static/89e8f06462a08babffc4d5d29add7030/12d18/change-key.png 375w,/static/89e8f06462a08babffc4d5d29add7030/44507/change-key.png 750w,/static/89e8f06462a08babffc4d5d29add7030/2e9ed/change-key.png 1500w,/static/89e8f06462a08babffc4d5d29add7030/5ddad/change-key.png 1588w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Refactoring keys&lt;/div&gt;&lt;p&gt;This allows us to refactor localization keys without losing translations or creating duplicate entries.&lt;/p&gt;</content:encoded><category>localization</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to use custom names for Swift properties when decoding JSON data]]></title><description><![CDATA[Learn how to map JSON keys to differently named Swift properties using CodingKeys.]]></description><link>https://tanaschita.com/swift-json-decoding-custom-names</link><guid isPermaLink="false">https://tanaschita.com/swift-json-decoding-custom-names</guid><pubDate>Sun, 08 Mar 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s &lt;code&gt;Codable&lt;/code&gt; protocol makes it straightforward to convert JSON data into native Swift types and vice versa. In some cases, however, we may want to customize how JSON keys map to Swift properties.&lt;/p&gt;&lt;p&gt;For example, the JSON returned by an API might use different naming conventions than our Swift models, or it might contain keys that we want to ignore.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to customize this behavior.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Types conforming to &lt;code&gt;Codable&lt;/code&gt; can declare a nested enumeration called &lt;code&gt;CodingKeys&lt;/code&gt; that conforms to the &lt;code&gt;CodingKey&lt;/code&gt; protocol. The cases of this enumeration define which properties participate in encoding and decoding.&lt;/p&gt;&lt;p&gt;By default, Swift expects the case names to match the property names.&lt;/p&gt;&lt;p&gt;Assume we receive the following JSON response from a server:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;username&amp;quot;: &amp;quot;user123&amp;quot;,
  &amp;quot;validated&amp;quot;: true
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And we want to map it to the following Swift type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct User: Codable {

    let username: String
    let isValidated: Bool
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the JSON key &lt;code&gt;validated&lt;/code&gt; does not match our Swift property name &lt;code&gt;isValidated&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To map the JSON key to our property, we can define a custom &lt;code&gt;CodingKeys&lt;/code&gt; enum:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct User: Codable {

    let username: String
    let isValidated: Bool

    enum CodingKeys: String, CodingKey {
        case username
        case isValidated = &amp;quot;validated&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By assigning a raw value to a case, we explicitly tell Swift which JSON key corresponds to the property.&lt;/p&gt;&lt;p&gt;During decoding, the value of the JSON key &lt;code&gt;validated&lt;/code&gt; will now be assigned to the &lt;code&gt;isValidated&lt;/code&gt; property of the &lt;code&gt;User&lt;/code&gt; struct.&lt;/p&gt;</content:encoded><category>json</category><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Implementing Passkeys in iOS with AuthenticationServices]]></title><description><![CDATA[A practical introduction to passwordless authentication using passkeys on iOS.]]></description><link>https://tanaschita.com/ios-authentication-passkeys</link><guid isPermaLink="false">https://tanaschita.com/ios-authentication-passkeys</guid><pubDate>Sun, 01 Mar 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Passkeys are credentials built to eliminate security problems like weak or reused passwords, credential leaks and phishing attacks.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at what passkeys are, how they work conceptually, and how to integrate them into an iOS app using Apple&amp;#x27;s &lt;code&gt;AuthenticationServices&lt;/code&gt; framework.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What are passkeys?&lt;/h3&gt;&lt;p&gt;Passkeys are a passwordless authentication method based on public-key cryptography. Instead of storing a password on a server, passkeys use:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A &lt;strong&gt;private key&lt;/strong&gt;, securely stored on the user&amp;#x27;s device.&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;public key&lt;/strong&gt;, stored on the server.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;When signing in, the server sends a cryptographic challenge. The device signs it using the private key, and the server verifies the signature using the public key.&lt;/p&gt;&lt;p&gt;The private key is securely retained on the device and synced with other devices, for example via iCloud Keychain.&lt;/p&gt;&lt;p&gt;Under the hood, passkeys are built on the WebAuthn and FIDO2 standards. As iOS developers, however, we do not need to implement those protocols manually, the system handles that for us.&lt;/p&gt;&lt;h3&gt;How passkeys work in an iOS app&lt;/h3&gt;&lt;p&gt;On iOS, passkeys are implemented using the &lt;code&gt;AuthenticationServices&lt;/code&gt; framework.&lt;/p&gt;&lt;p&gt;There are two main flows:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Registration&lt;/strong&gt; - creating a new passkey.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt; - signing in using an existing passkey.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In both cases, the backend plays a crucial role. It must:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Generate cryptographic challenges&lt;/li&gt;&lt;li&gt;Store public keys&lt;/li&gt;&lt;li&gt;Verify signed responses&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The iOS app acts as a client in the WebAuthn flow.&lt;/p&gt;&lt;h3&gt;Registering a passkey&lt;/h3&gt;&lt;p&gt;A typical &amp;quot;sign up with passkey&amp;quot; flow looks like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The user enters an identifier (for example an email) on the sign-up screen.&lt;/li&gt;&lt;li&gt;The app asks the backend to start passkey registration.&lt;/li&gt;&lt;li&gt;The backend returns a registration challenge and a stable user handle (a backend-generated user ID).&lt;/li&gt;&lt;li&gt;iOS creates the passkey and returns attestation data.&lt;/li&gt;&lt;li&gt;The app sends the attestation back to the backend, which verifies it and creates the account.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The same flow also works when a user is already signed in and wants to add a passkey as an additional login method.&lt;/p&gt;&lt;p&gt;After obtaining a challenge from the server, we can create an authentication request which presents a sheet to the user asking to create a new credential:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
    relyingPartyIdentifier: &amp;quot;example.com&amp;quot;)

let registrationRequest = provider.createCredentialRegistrationRequest(
    challenge: challenge, displayName: email, userID: userID)

let authController = ASAuthorizationController([platformKeyRequest])
authController.delegate = self
authController.presentationContextProvider = self
authController.performRequests()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;relyingPartyIdentifier&lt;/code&gt; must match the associated domain configured via the Associated Domains capability.&lt;/p&gt;&lt;p&gt;When the user confirms with Face ID or Touch ID, the system creates a new key pair and returns a credential. In the delegate callback:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func authorizationController(
    controller: ASAuthorizationController,
    didCompleteWithAuthorization authorization: ASAuthorization
) {
    if let credential = authorization.credential
        as? ASAuthorizationPlatformPublicKeyCredentialRegistration {

        let credentialID = credential.credentialID
        let clientDataJSON = credential.rawClientDataJSON
        let attestationObject = credential.rawAttestationObject

        // Send these values to backend for verification
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The backend then verifies the registration and stores the public key.&lt;/p&gt;&lt;h3&gt;Authenticating with a passkey&lt;/h3&gt;&lt;p&gt;The login flow is similar. The only difference is, that we now create an assertion request instead of a registration request:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let request = credetialProvider.createCredentialAssertionRequestWithChallenge(challenge)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When providing an assertion request to the controller and the user has one or more credentials on the device, the device displays a sheet with the list of credentials to choose from. If there aren&amp;#x27;t any credentials on the device, we get an error. In this case, we can ask the user to register.&lt;/p&gt;&lt;p&gt;Based on the credential that is passed into &lt;code&gt;authorizationController(controller:didCompleteWithAuthorization:)&lt;/code&gt;, we can determine the request type and take further steps.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func authorizationController(
    controller: ASAuthorizationController,
    didCompleteWithAuthorization authorization: ASAuthorization
) {
    switch authorization.credential {
    case let registration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
        // Send attestation data to backend (finish registration)

    case let assertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
        let signature = assertion.signature
        let clientDataJSON = assertion.rawClientDataJSON
        let authenticatorData = assertion.rawAuthenticatorData
        let credentialID = assertion.credentialID

        // Send signature and related data to backend (finish login)

    default:
        break
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For authentication, the backend verifies the signature using the stored public key. If valid, the user is signed in.&lt;/p&gt;</content:encoded><category>authentication</category><category>security</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Preventing forgotten database migrations with automated tests in iOS]]></title><description><![CDATA[Learn how to automatically detect migration errors before shipping.]]></description><link>https://tanaschita.com/testing-database-migrations</link><guid isPermaLink="false">https://tanaschita.com/testing-database-migrations</guid><pubDate>Sat, 21 Feb 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with persistence in iOS apps, schema changes are inevitable. For example when adding, removing or modifying properties, renaming attributes, changing relationships, or refactoring model structures.&lt;/p&gt;&lt;p&gt;On a clean install, the database is created with the newest schema so everything works. Existing users, however, already have a store created with an older schema. If the app cannot migrate that store, startup may fail. Ensuring that migrations are validated automatically can prevent breaking existing installations.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at a strategy to automatically detect migration errors before shipping.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Migrations across common frameworks&lt;/h3&gt;&lt;p&gt;Different frameworks such as SwiftData, Core Data, Realm, or manual solutions like GRDB and SQLite, handle migrations slightly differently.&lt;/p&gt;&lt;p&gt;Some frameworks attempt to infer certain schema changes automatically and support lightweight migrations. Others always require explicit migration logic. The level of automation varies, but the risk of forgetting a required migration remains.&lt;/p&gt;&lt;h3&gt;What does a meaningful migration test need to prove?&lt;/h3&gt;&lt;p&gt;A migration test should answer the following question: Can the current app version open a database created by a previous version?&lt;/p&gt;&lt;p&gt;In manual testing, this is done by installing a published app version and then trying to update to the new app version that contains schema changes. To automate this in a test, we&amp;#x27;ll use the same principle.&lt;/p&gt;&lt;h3&gt;Testing with a database from a previous version&lt;/h3&gt;&lt;p&gt;The idea is the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Add a baseline database file created with an older app version to the test target.&lt;/li&gt;&lt;li&gt;Copy that file into a temporary directory during a test.&lt;/li&gt;&lt;li&gt;Attempt to open it using the current persistence stack.&lt;/li&gt;&lt;li&gt;Fail if opening throws an error.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Conceptually, a test might look like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Testing

@Test
func previousStoreCanBeOpened() throws {
    let storeURL = try copyBaselineStoreToTemporaryDirectory()
    let container = makeProductionPersistentContainer(storeURL: storeURL)

    try container.loadPersistentStores()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If a migration is missing or incompatible, loading the store throws an error and the test fails.&lt;/p&gt;&lt;p&gt;To cover both the full migration chain and the most realistic upgrade path, a strategy can be to maintain two baseline stores:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A store created several versions ago (tests the entire chain)&lt;/li&gt;&lt;li&gt;A store from the last released version (tests a common upgrade path)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Maintaining the &amp;quot;last release&amp;quot; store introduces additional effort, since it needs to be updated after each shipped schema change. Whether this is worth it depends on the project. For teams that already perform thorough manual upgrade testing, keeping a single older baseline may provide sufficient safety.&lt;/p&gt;&lt;h3&gt;How do I create a database file?&lt;/h3&gt;&lt;p&gt;The easiest way to create such a file is to run the app and let it create its database naturally. After that, the database file can be copied from the simulator or device container and added to the test target.&lt;/p&gt;&lt;p&gt;The exact file depends on the persistence framework:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;with Core Data, it is typically a &lt;code&gt;.sqlite&lt;/code&gt; file (and possibly its &lt;code&gt;-wal&lt;/code&gt; and &lt;code&gt;-shm&lt;/code&gt; companions).&lt;/li&gt;&lt;li&gt;with SwiftData, it is also backed by SQLite and follows the same structure.&lt;/li&gt;&lt;li&gt;with Realm, it is a &lt;code&gt;.realm&lt;/code&gt; file.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;On the iOS simulator, the database can usually be found inside the app container under:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;~/Library/Developer/CoreSimulator/Devices/&amp;lt;DeviceID&amp;gt;/data/Containers/Data/Application/&amp;lt;AppID&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Can the baseline store be generated automatically?&lt;/h3&gt;&lt;p&gt;Updating and replacing the baseline store after a schema change does involve some manual work. At first glance, it might seem cleaner to generate the baseline store automatically during the test run instead of maintaining fixture files. Conceptually, that could look something like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Testing

@Test
func migrationFromPreviousSchemaSucceeds() throws {
    let oldStoreURL = try createStoreWithPreviousSchema()
    try openStoreWithCurrentSchema(at: oldStoreURL)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;#x27;s easy to automate creating a store file. The hard part is creating it with an older schema.&lt;/p&gt;&lt;p&gt;To programmatically generate a &amp;quot;v2 store&amp;quot;, the test must still have access to the &amp;quot;v2 model definition&amp;quot;. For example, in Core Data, that usually means keeping older &lt;code&gt;.xcdatamodel&lt;/code&gt; versions around and loading them explicitly. In Realm, we would need to keep the older objects somehow.&lt;/p&gt;&lt;p&gt;So it introduces maintenance work of a different kind:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Older model versions must remain in the project and stay consistent with what was actually shipped.&lt;/li&gt;&lt;li&gt;Tests need a reliable way to load a specific historical model version.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Because of that, in most cases, storing baseline files is the clearer and easier solution.&lt;/p&gt;</content:encoded><category>testing</category><category>persistence</category><category>core data</category><category>swiftdata</category></item><item><title><![CDATA[How to build a simple CLI tool using Swift]]></title><description><![CDATA[Create small automation tools with Swift and ArgumentParser.]]></description><link>https://tanaschita.com/swift-cli-scripting</link><guid isPermaLink="false">https://tanaschita.com/swift-cli-scripting</guid><pubDate>Sun, 15 Feb 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working on iOS projects, we often need small automation tools. Maybe to run scripts in CI, automate repetitive tasks, process files or interact with APIs. While bash or Python are common choices, using Swift can be a great alternative, especially if you&amp;#x27;re already comfortable with it.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll build a simple command-line tool using Swift. Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What we&amp;#x27;re going to build&lt;/h3&gt;&lt;p&gt;To make things practical, we&amp;#x27;ll build a small developer utility which prints the latest Xcode version. The goal is to be able to run:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;swift run devtools latest-xcode
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and get a result such as:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Xcode (Apple Silicon): 26.3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our tool will:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;fetch release data from a public JSON endpoint&lt;/li&gt;&lt;li&gt;decode the response using &lt;code&gt;Codable&lt;/code&gt;&lt;/li&gt;&lt;li&gt;print the latest Xcode version&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;This small example will already demonstrate the core building blocks of many real-world automation tools such as structured commands, argument parsing and async networking.&lt;/p&gt;&lt;h3&gt;Creating an executable Swift package&lt;/h3&gt;&lt;p&gt;Swift Package Manager can generate an executable project for us:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir devtools
cd devtools
swift package init --type executable
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The package name is derived from the folder name (&lt;code&gt;devtools&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;We can already run it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;swift run
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the folder, the files &lt;code&gt;Package.swift&lt;/code&gt; and &lt;code&gt;devtools.swift&lt;/code&gt; were created for us. The folder can be opened with an IDE such as Xcode for implementation.&lt;/p&gt;&lt;h3&gt;Adding argument parsing&lt;/h3&gt;&lt;p&gt;For structured arguments and built-in help output, Apple provides an official package called Swift Argument Parser. We can add it to &lt;code&gt;Package.swift&lt;/code&gt; as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;dependencies: [
    .package(
        url: &amp;quot;https://github.com/apple/swift-argument-parser.git&amp;quot;,
        from: &amp;quot;1.3.0&amp;quot;
    )
],
targets: [
    .executableTarget(
        name: &amp;quot;devtools&amp;quot;,
        dependencies: [
            .product(
                name: &amp;quot;ArgumentParser&amp;quot;,
                package: &amp;quot;swift-argument-parser&amp;quot;
            )
        ]
    )
]
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Defining the command structure&lt;/h3&gt;&lt;p&gt;Now we can replace the generated code in &lt;code&gt;devtools.swift&lt;/code&gt; with a structured command setup:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import ArgumentParser
import Foundation

@main
struct DevTools: AsyncParsableCommand {

    static let configuration = CommandConfiguration(
        abstract: &amp;quot;Developer utilities.&amp;quot;,
        subcommands: [LatestXcodeCommand.self]
    )
}

struct LatestXcodeCommand: AsyncParsableCommand {

    static let configuration = CommandConfiguration(
        commandName: &amp;quot;latest-xcode&amp;quot;,
        abstract: &amp;quot;Print the latest Xcode version.&amp;quot;
    )

    func run() async throws {
        print(&amp;quot;TODO: Here, we will fetch and show the latest Xcode version.&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way, we define a root command (&lt;code&gt;devtools&lt;/code&gt;) that can host multiple subcommands and define &lt;code&gt;LatestXcodeCommand&lt;/code&gt; as a sub command.&lt;/p&gt;&lt;p&gt;Now, we can already run &lt;code&gt;swift run devtools --help&lt;/code&gt; which will print available sub commands, in our case &lt;code&gt;latest-xcode&lt;/code&gt;. And we can already try out &lt;code&gt;swift run devtools latest-xcode&lt;/code&gt;, which will currently print our &lt;code&gt;TODO: ..&lt;/code&gt; statement from the &lt;code&gt;run&lt;/code&gt; method.&lt;/p&gt;&lt;h3&gt;Fetching and showing the latest Xcode version&lt;/h3&gt;&lt;p&gt;Apple provides a public endpoint to fetch xcode release data: &lt;code&gt;https://xcodereleases.com/data.json&lt;/code&gt;. First, we define a struct which represents the relevant data of the json:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct XcodeRelease: Decodable {
    let name: String
    let version: Version
    let dateOrder: Int

    struct Version: Decodable {
        let number: String
    }

    enum CodingKeys: String, CodingKey {
        case name
        case version
        case dateOrder = &amp;quot;_dateOrder&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The fetch method inside &lt;code&gt;LatestXcodeCommand&lt;/code&gt; might then look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetchReleases() async throws -&amp;gt; [XcodeRelease] {
    let url = URL(string: &amp;quot;https://xcodereleases.com/data.json&amp;quot;)!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let http = response as? HTTPURLResponse,
            (200..&amp;lt;300).contains(http.statusCode)
    else {
        throw URLError(.badServerResponse)
    }
    return try JSONDecoder().decode([XcodeRelease].self, from: data)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br/&gt;
If you opened the package in Xcode, make sure to select the executable scheme (e.g. &lt;code&gt;devtools&lt;/code&gt;) and run it on &lt;strong&gt;My Mac&lt;/strong&gt;. Otherwise, Xcode might try to build it for iOS, which can lead to availability errors when using Swift concurrency.&lt;/p&gt;&lt;p&gt;Now, we can use it inside the &lt;code&gt;run&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct LatestXcodeCommand: AsyncParsableCommand {

    func run() async throws {
        let releases = try await fetchReleases()

        guard let latest = releases
            .sorted(by: { $0.dateOrder &amp;gt; $1.dateOrder })
            .first
        else {
            throw ValidationError(&amp;quot;No release found.&amp;quot;)
        }

        print(&amp;quot;\(latest.name): \(latest.version.number)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command fetches Xcode release data, sorts them by date and prints the newest one. To keep it simple, the current command does not filter for stable releases.&lt;/p&gt;&lt;p&gt;Now, when running &lt;code&gt;swift run devtools latest-xcode&lt;/code&gt; again, we&amp;#x27;ll get our desired result:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Xcode (Apple Silicon): 26.3
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Building a standalone binary&lt;/h3&gt;&lt;p&gt;So far, we have been running the tool using the debug mode. To generate an optimized standalone binary, we can create a release build:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;swift build -c release
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This produces a compiled executable located at:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.build/release/devtools
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The generated file is a native macOS executable. It does not require Xcode to run. Only the Swift runtime, which is already available on macOS systems with Xcode installed.&lt;/p&gt;&lt;p&gt;We can now copy the executable anywhere or use it in CI pipelines. For example after copying it to &lt;code&gt;/usr/local/bin&lt;/code&gt;, the command will be globally available:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;devtools latest-xcode
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;With Swift Package Manager and Swift Argument Parser, building clean and maintainable CLI tools in Swift is straightforward. In just a few steps, we created a structured CLI, argument parsing, async networking and JSON decoding.&lt;/p&gt;</content:encoded><category>scripting</category><category>automation</category><category>swift package manager</category><category>swift</category></item><item><title><![CDATA[How to use SwiftUI Coordinators to communicate with UIKit]]></title><description><![CDATA[Bridging UIKit delegates and events back into SwiftUI.]]></description><link>https://tanaschita.com/swiftui-coordinators</link><guid isPermaLink="false">https://tanaschita.com/swiftui-coordinators</guid><pubDate>Sun, 08 Feb 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building SwiftUI apps, we sometimes still need UIKit. Maybe we&amp;#x27;re integrating an older code base or using system controllers that don&amp;#x27;t yet have a native SwiftUI equivalent.&lt;/p&gt;&lt;p&gt;In those cases, we can wrap UIKit views or view controllers using &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;. But as soon as delegates or callbacks are involved, we need a way to communicate back to SwiftUI. That&amp;#x27;s where &lt;strong&gt;coordinators&lt;/strong&gt; come in.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at a practical example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The goal&lt;/h3&gt;&lt;p&gt;Suppose we want to use Apple&amp;#x27;s &lt;code&gt;DataScannerViewController&lt;/code&gt; to scan QR codes. At the time of writing, &lt;code&gt;DataScannerViewController&lt;/code&gt; does not have a native SwiftUI equivalent yet.&lt;/p&gt;&lt;p&gt;From SwiftUI, we want to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;present the scanner&lt;/li&gt;&lt;li&gt;scan a code&lt;/li&gt;&lt;li&gt;pass the scanned value back to SwiftUI state&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Wrapping the view controller&lt;/h3&gt;&lt;p&gt;To embed a UIKit view controller inside SwiftUI, we conform to &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ScannerView: UIViewControllerRepresentable {

    @Binding var code: String?

    func makeUIViewController(context: Context) -&amp;gt; DataScannerViewController {
        let controller = DataScannerViewController(
            recognizedDataTypes: [.barcode(symbologies: [.qr])]
        )
        return controller
    }

    func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can present it like any other SwiftUI view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var code: String?
    @State private var showingScanner = false

    var body: some View {
        VStack {
            Text(code ?? &amp;quot;&amp;quot;)
            Button(&amp;quot;Scan QR code&amp;quot;) {
                showingScanner = true
            }
        }
        .sheet(isPresented: $showingScanner) {
            ScannerView(code: $code)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So far, the &lt;code&gt;code&lt;/code&gt; binding we pass into the scanner view never gets updated by it. To be able to do that, we need to setup the scanner view controller&amp;#x27;s delegate at first.&lt;/p&gt;&lt;h3&gt;Why we need a coordinator&lt;/h3&gt;&lt;p&gt;UIKit delegates require a reference type (class). But SwiftUI views are structs. So our &lt;code&gt;ScannerView&lt;/code&gt; cannot directly act as the delegate.&lt;/p&gt;&lt;p&gt;SwiftUI solves this mismatch by providing coordinators which act as small bridge objects that:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;conform to delegates&lt;/li&gt;&lt;li&gt;receive UIKit callbacks&lt;/li&gt;&lt;li&gt;forward data back to SwiftUI&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Creating a coordinator&lt;/h3&gt;&lt;p&gt;Every representable can define a nested &lt;code&gt;Coordinator&lt;/code&gt; type and a &lt;code&gt;makeCoordinator()&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ScannerView: UIViewControllerRepresentable {

    @Binding var code: String?

    func makeCoordinator() -&amp;gt; Coordinator {
        Coordinator(code: $code)
    }

    class Coordinator {
        @Binding var code: String?

        init(code: Binding&amp;lt;String?&amp;gt;) {
            _code = code
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SwiftUI keeps this coordinator alive for the lifetime of the representable.&lt;/p&gt;&lt;h3&gt;Conforming to the delegate&lt;/h3&gt;&lt;p&gt;We can then extend the coordinator to serve as &lt;code&gt;DataScannerViewControllerDelegate&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class Coordinator: DataScannerViewControllerDelegate {

    @Binding var code: String?

    init(code: Binding&amp;lt;String?&amp;gt;) {
        _code = code
    }

    func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
        guard let item = allItems.first else { return }
        switch item {
        case .barcode(let code):
            code = code.payloadStringValue
        default: break
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now the coordinator receives scan results and updates the binding.&lt;/p&gt;&lt;h3&gt;Connecting everything&lt;/h3&gt;&lt;p&gt;Finally, we assign the delegate inside &lt;code&gt;makeUIViewController&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func makeUIViewController(context: Context) -&amp;gt; DataScannerViewController {
    let controller = DataScannerViewController(
        recognizedDataTypes: [.barcode(symbologies: [.qr])]
    )

    controller.delegate = context.coordinator
    try? controller.startScanning()

    return controller
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;context.coordinator&lt;/code&gt; gives us the instance SwiftUI created earlier.&lt;/p&gt;</content:encoded><category>swiftui</category><category>uikit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Migrating to the Observation framework in SwiftUI]]></title><description><![CDATA[Learn how to use SwiftUI's @Observable macro.]]></description><link>https://tanaschita.com/swiftui-observation-migrating-to-observation</link><guid isPermaLink="false">https://tanaschita.com/swiftui-observation-migrating-to-observation</guid><pubDate>Sun, 01 Feb 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI&amp;#x27;s current state-of-the-art approach to state observation is the Observation framework. It uses the macro system in Swift to transform Swift types into observable state.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how Observation differs from the older &lt;code&gt;ObservableObject&lt;/code&gt; and &lt;code&gt;@Published&lt;/code&gt; pattern, and how to migrate existing SwiftUI code step by step.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;How Observation differs conceptually&lt;/h3&gt;&lt;p&gt;Observation isn&amp;#x27;t just a syntax change, it introduces property-level tracking.&lt;/p&gt;&lt;p&gt;Unlike &lt;code&gt;ObservableObject&lt;/code&gt;, which broadcasts change notifications for the entire object, Observation tracks access to individual properties. SwiftUI only updates views when a property they actually read changes.&lt;/p&gt;&lt;p&gt;This reduces unnecessary updates and makes view invalidation more precise.&lt;/p&gt;&lt;h3&gt;Migration steps&lt;/h3&gt;&lt;p&gt;Observation comes with a &lt;code&gt;@Observable&lt;/code&gt; macro which automatically adds observation support to a Swift type. To start using it, the following migration steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Replace &lt;code&gt;ObservableObject&lt;/code&gt; with &lt;code&gt;@Observable&lt;/code&gt; macro&lt;/li&gt;&lt;li&gt;Replace &lt;code&gt;@StateObject&lt;/code&gt; with &lt;code&gt;@State&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Replace &lt;code&gt;@ObservedObject&lt;/code&gt; with &lt;code&gt;@Bindable&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Replace &lt;code&gt;@EnvironmentObject&lt;/code&gt; with &lt;code&gt;@Environment&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Not all projects will need every step, but these changes cover the most common migration paths from &lt;code&gt;ObservableObject&lt;/code&gt;-based code.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at these steps in more detail.&lt;/p&gt;&lt;h3&gt;1. Replace &lt;code&gt;ObservableObject&lt;/code&gt; with &lt;code&gt;@Observable&lt;/code&gt; macro&lt;/h3&gt;&lt;p&gt;To replace &lt;code&gt;ObservableObject&lt;/code&gt; with the &lt;code&gt;@Observable&lt;/code&gt; macro, we need to remove conformances to &lt;code&gt;ObservableObject&lt;/code&gt;, remove all &lt;code&gt;@Published&lt;/code&gt; annotations, add &lt;code&gt;@ObservationIgnored&lt;/code&gt; where needed and apply the &lt;code&gt;Observable&lt;/code&gt; macro instead.&lt;/p&gt;&lt;p&gt;Here is an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;/// BEFORE
class UserViewModel: ObservableObject {
    @Published var name: String?
    @Published var email: String?

    var authenticationService: AuthenticationService
}

/// AFTER
@Observable class UserViewModel {
    var name: String? = nil
    var email: String? = nil

    @ObservationIgnored
    var authenticationService: AuthenticationService
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Properties marked with &lt;code&gt;@ObservationIgnored&lt;/code&gt; are excluded from observation and do not trigger view updates. This is an important migration detail that may easily be missed.&lt;/p&gt;&lt;h3&gt;2. Replace &lt;code&gt;@StateObject&lt;/code&gt; with &lt;code&gt;@State&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Before the Observation framework, we used &lt;code&gt;@State&lt;/code&gt; for value types and &lt;code&gt;@StateObject&lt;/code&gt; for reference types to let SwiftUI update views whenever changes occured. With Observation, both types are covered by &lt;code&gt;@State&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;/// BEFORE
struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        ...
    }
}

// AFTER
struct UserView: View {
    @State var viewModel = UserViewModel()

    var body: some View {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. Replace &lt;code&gt;@ObservedObject&lt;/code&gt; with &lt;code&gt;@Bindable&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Before the Observation framework, we used &lt;code&gt;@Binding&lt;/code&gt; for value types and &lt;code&gt;@ObservedObject&lt;/code&gt; for reference types to create two-way bindings. With Observation, we use &lt;code&gt;@Binding&lt;/code&gt; for value types and &lt;code&gt;@Bindable&lt;/code&gt; for reference types:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// BEFORE
struct UserView: View {
    @ObservedObject var viewModel: UserViewModel

    var body: some View {
        ...
    }
}

// AFTER
struct UserView: View {
    @Bindable var viewModel: UserViewModel

    var body: some View {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you want to better understand when and why &lt;code&gt;@Bindable&lt;/code&gt; is required, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/swiftui-observation-bindable/&quot;&gt;how @Bindable works in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;4. Replace &lt;code&gt;@EnvironmentObject&lt;/code&gt; with &lt;code&gt;@Environment&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Instead of the &lt;code&gt;@EnvironmentObject&lt;/code&gt; property wrapper, the Observation framework uses the &lt;code&gt;@Environment&lt;/code&gt; property wrapper to pass objects down the view hierarchy:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// BEFORE
struct UserView: View {
    @EnvironmentObject var viewModel: UserViewModel

    var body: some View {
        ...
    }
}

// AFTER
struct UserView: View {
    @Environment(UserViewModel.self) private var viewModel

    var body: some View {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Migrating incrementally and understanding these conceptual changes helps avoid subtle issues and keeps SwiftUI state predictable as projects evolve.&lt;/p&gt;</content:encoded><category>observation</category><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[A visual cheat sheet of Combine operators]]></title><description><![CDATA[A diagram-based reference for common Combine publisher operators.]]></description><link>https://tanaschita.com/combine-operators-cheatsheet</link><guid isPermaLink="false">https://tanaschita.com/combine-operators-cheatsheet</guid><pubDate>Sun, 25 Jan 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article provides a visual cheat sheet of common Combine operators, with a diagram for each one to illustrate how values flow through a pipeline. The diagrams focus on the most commonly used operators.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Note: The diagrams below are generated using SwiftUI. If you&amp;#x27;re interested in how they are built, you can find the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/Tanaschita/combineOperators&quot;&gt;source code on GitHub&lt;/a&gt;.&lt;/p&gt;&lt;combineOperators&gt;&lt;/combineOperators&gt;</content:encoded><category>combine</category><category>reactive programming</category><category>swift</category></item><item><title><![CDATA[How to use String Catalogs for pluralization in Swift]]></title><description><![CDATA[Learn how to handle plural forms correctly with String Catalogs.]]></description><link>https://tanaschita.com/ios-string-catalogs-pluralization</link><guid isPermaLink="false">https://tanaschita.com/ios-string-catalogs-pluralization</guid><pubDate>Sun, 18 Jan 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;String Catalogs provide a type-safe way to handle plural forms across languages. In this article, we look at how to define pluralized strings in String Catalogs and how to use them from Swift without manual formatting or conditional logic.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Defining pluralized strings in a String Catalog&lt;/h3&gt;&lt;p&gt;To define a pluralized string, we start by creating a localization key as usual, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;applesSelection.title
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To enable pluralization, we right-click the key-value pair in the String Catalog and choose &lt;strong&gt;“Vary by Plural”&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Xcode then automatically generates the required plural categories for the current language. What remains is adjusting the translations for the different plural cases. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;%lld apple // For one
%lld apples // For other
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The format specifier (&lt;code&gt;%lld&lt;/code&gt;) tells the localization system that this string expects a numeric value, which will later drive pluralization.&lt;/p&gt;&lt;p&gt;Which plural forms are shown depends on the language. Some languages only require &lt;code&gt;one&lt;/code&gt; and &lt;code&gt;other&lt;/code&gt;, while others define additional categories.&lt;/p&gt;&lt;h3&gt;Using the pluralized string in Swift and SwiftUI&lt;/h3&gt;&lt;p&gt;Once a plural variable is defined, Xcode generates a &lt;strong&gt;typed accessor&lt;/strong&gt; for the localized string. We can use it as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Text(.applesSelectionTitle(1))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Outside SwiftUI, we can use the same accessor:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let text = String(localized: .applesSelectionTitle(5))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In both cases, the value we pass determines which plural form is selected for the current locale. There is no string interpolation and no conditional logic in code, the localization system handles everything.&lt;/p&gt;</content:encoded><category>localization</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Memory management in Combine]]></title><description><![CDATA[Understand how retain cycles appear and when they turn into memory leaks in Combine.]]></description><link>https://tanaschita.com/combine-memory-management</link><guid isPermaLink="false">https://tanaschita.com/combine-memory-management</guid><pubDate>Sun, 11 Jan 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Combine allows us to model asynchronous workflows declaratively. Publishers emit values, subscribers react to them, and operators connect everything into a clear pipeline.&lt;/p&gt;&lt;p&gt;What&amp;#x27;s easy to overlook is that Combine also introduces a specific ownership model. If we don&amp;#x27;t understand who holds references to whom, objects may live longer than expected, or never be released at all.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how memory management works in Combine.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The ownership model in Combine&lt;/h3&gt;&lt;p&gt;When we subscribe to a publisher using operators like &lt;code&gt;sink&lt;/code&gt; or &lt;code&gt;assign&lt;/code&gt;, Combine creates:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;a subscriber&lt;/li&gt;&lt;li&gt;an &lt;code&gt;AnyCancellable&lt;/code&gt;, which represents the subscription&amp;#x27;s lifetime&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A simple rule of thumb is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Whoever owns the &lt;code&gt;AnyCancellable&lt;/code&gt; owns the subscription.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;As long as the cancellable exists, the subscription remains active.&lt;/p&gt;&lt;h3&gt;How retain cycles appear in Combine&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s look at the following example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var cancellable: AnyCancellable?

func startTimer() {
    cancellable = Timer.publish(every: 1, on: .main, in: .default)
        .autoconnect()
        .sink { _ in
            self.tick()
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This creates a retain cycle:&lt;/p&gt;&lt;p&gt;&lt;code&gt;self → cancellable → subscriber → closure → self&lt;/code&gt;&lt;/p&gt;&lt;p&gt;An important thing to understand is that a retain cycle not always leads to a memory leak.&lt;/p&gt;&lt;h3&gt;Why a retain cycle does not always become a memory leak&lt;/h3&gt;&lt;p&gt;A retain cycle is a structural issue: two or more objects hold strong references to each other, forming a cycle. A memory leak is a behavioral problem: an object remains in memory longer than intended and can never be released.&lt;/p&gt;&lt;p&gt;These two concepts are related, but they are not the same. A retain cycle can cause a memory leak, but only if nothing ever breaks the cycle.&lt;/p&gt;&lt;p&gt;For example, if the publisher completes, Combine tears down the subscription, so the closure gets released. This is why retain cycles caused by short-lived subscriptions do not turn into memory leaks.&lt;/p&gt;&lt;p&gt;Examples of short-lived publishers are network requests, &lt;code&gt;Just&lt;/code&gt;, &lt;code&gt;Future&lt;/code&gt;, finite sequences. In these cases, the retain cycle exists only temporarily and resolves itself automatically.&lt;/p&gt;&lt;h3&gt;Long-lived subscriptions are different&lt;/h3&gt;&lt;p&gt;Some publishers never complete on their own:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;timers&lt;/li&gt;&lt;li&gt;&lt;code&gt;NotificationCenter&lt;/code&gt; publishers&lt;/li&gt;&lt;li&gt;subjects&lt;/li&gt;&lt;li&gt;continuous streams&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With these publishers, the retain cycle remains in place until we explicitly break it.&lt;/p&gt;&lt;p&gt;If we never cancel the subscription and capture &lt;code&gt;self&lt;/code&gt; strongly, the cycle becomes a real memory leak.&lt;/p&gt;&lt;h3&gt;Cancellation breaks the cycle&lt;/h3&gt;&lt;p&gt;Explicit cancellation has the same effect as completion.&lt;/p&gt;&lt;p&gt;When we cancel a subscription or release the &lt;code&gt;AnyCancellable&lt;/code&gt;, Combine tears down the subscription and releases the subscriber and closure.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func stopTimer() {
    cancellable = nil
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this in place the retain cycle still exists while the timer is running. Once cancelled, the cycle is resolved. This is an important distinction: the code does create a retain cycle, but it does not necessarily leak. Of course, we risk a memory leak, for example when forgetting to call &lt;code&gt;stopTimer&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Using weak self&lt;/h3&gt;&lt;p&gt;To avoid retain cycles entirely, we can capture self weakly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.sink { [weak self] _ in
    self?.tick()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This breaks the cycle proactively. When &lt;code&gt;self&lt;/code&gt; is deallocated, the subscription is cancelled automatically. This approach is especially important for long-lived subscriptions where cancellation is not guaranteed.&lt;/p&gt;&lt;p&gt;In practice:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If we capture strongly, we must guarantee cancellation&lt;/li&gt;&lt;li&gt;If cancellation is uncertain, weak captures are safer&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In practice, using &lt;code&gt;[weak self]&lt;/code&gt; is often the safest default, especially when the lifetime of a subscription is unclear or may change over time.&lt;/p&gt;</content:encoded><category>combine</category><category>reactive programming</category><category>swift</category></item><item><title><![CDATA[Storing Swift structs in UserDefaults]]></title><description><![CDATA[Why Codable works but often isn't the best choice.]]></description><link>https://tanaschita.com/swift-user-defaults-storing-structs</link><guid isPermaLink="false">https://tanaschita.com/swift-user-defaults-storing-structs</guid><pubDate>Sun, 04 Jan 2026 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;UserDefaults&lt;/code&gt; is often the first place we reach for when we want to persist small pieces of data. It&amp;#x27;s simple, built into the system, and works well for basic types like strings, numbers, and booleans.&lt;/p&gt;&lt;p&gt;But what if we try to store a Swift struct in &lt;code&gt;UserDefaults&lt;/code&gt;. Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What UserDefaults can store by default&lt;/h3&gt;&lt;p&gt;Out of the box, UserDefaults only supports data types which are also supported by property lists. Those are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Bool&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Float&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Double&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Int&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;String&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;URL&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Date&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Data&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can also store arrays and dictionaries but only if their elements are one of the types listed above.&lt;/p&gt;&lt;h3&gt;Storing a struct in UserDefaults&lt;/h3&gt;&lt;p&gt;When we try to store a Swift struct in UserDefaults, the compiler does not complain:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Content {
    let title: String
}

let content = Content(title: &amp;quot;Some title&amp;quot;)

let userDefaults = UserDefaults.standard
userDefaults.set(content, forKey: &amp;quot;content&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, once we run the app, it crashes with the following exception:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Exception: Attempt to insert non-property list object for key content
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To store a Swift struct in UserDefaults, we need to convert it to one of the supported types. One common approach is to use &lt;code&gt;Codable&lt;/code&gt; and encode the struct into &lt;code&gt;Data&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Content: Codable {
    let title: String
}

if let contentData = try? JSONEncoder().encode(content) {
    userDefaults.set(contentData, forKey: &amp;quot;content&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To read the stored data, we can use a JSON decoder:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let contentData = userDefaults.data(forKey: &amp;quot;content&amp;quot;),
    let content = try? JSONDecoder().decode(Content.self, from: contentData) {
    print(content)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This pattern is easy to implement. But it comes with an important caveat.&lt;/p&gt;&lt;h3&gt;Why the Codable approach is error-prone&lt;/h3&gt;&lt;p&gt;The biggest issue with this approach appears when our model changes.&lt;/p&gt;&lt;p&gt;If we later modify &lt;code&gt;Content&lt;/code&gt;, for example by renaming a property, changing its type or by adding another property, the JSON decoder will not be able to decode a previously saved object.&lt;/p&gt;&lt;p&gt;Unlike a database-based storage, there&amp;#x27;s no built-in migration mechanism. Since we don&amp;#x27;t get any compiler hints or errors when changing the struct, this kind of storage may easily lead to bugs or lost data.&lt;/p&gt;&lt;h3&gt;An alternative: storing properties individually&lt;/h3&gt;&lt;p&gt;If we want more control and resilience to future changes, we can store individual properties instead of encoding the entire struct:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func writeContent(_ content: Content) {
    userDefaults.set(content.title, forKey: &amp;quot;contentTitle&amp;quot;)
    userDefaults.set(content.amount, forKey: &amp;quot;contentAmount&amp;quot;)
}

func readContent() -&amp;gt; Content {
    let title = userDefaults.string(forKey: &amp;quot;contentTitle&amp;quot;) ?? &amp;quot;&amp;quot;
    let amount = (userDefaults.object(forKey:) as? Int) ?? 0
    return Content(title: title, amount: amount)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this approach, we gain flexibility:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We can add or remove properties without breaking decoding&lt;/li&gt;&lt;li&gt;We can provide sensible default values&lt;/li&gt;&lt;li&gt;Renaming properties becomes much safer&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The trade-off is a bit more manual work, but in return, we get more predictable behavior as our app evolves.&lt;/p&gt;</content:encoded><category>swift</category><category>persistence</category><category>ios</category></item><item><title><![CDATA[Understanding date decoding strategies when working with JSON in Swift]]></title><description><![CDATA[Learn how Swift decodes dates and how to handle real-world formats.]]></description><link>https://tanaschita.com/swift-json-date-decoding-encoding</link><guid isPermaLink="false">https://tanaschita.com/swift-json-date-decoding-encoding</guid><pubDate>Sun, 28 Dec 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Working with JSON in Swift is usually straightforward. Until dates enter the picture. APIs rarely agree on a single date format, and a mismatch between what the backend sends and what &lt;code&gt;JSONDecoder&lt;/code&gt; expects can lead to confusing decoding failures.&lt;/p&gt;&lt;p&gt;Swift gives us several date decoding strategies to handle this. Let&amp;#x27;s take a closer look.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The default behavior&lt;/h3&gt;&lt;p&gt;By default, &lt;code&gt;JSONDecoder&lt;/code&gt; uses the &lt;code&gt;.deferredToDate&lt;/code&gt; strategy which is Apple&amp;#x27;s own date format.&lt;/p&gt;&lt;p&gt;This strategy only works if the JSON value is encoded in a very specific way. For example, when decoding data that was also encoded by &lt;code&gt;JSONEncoder&lt;/code&gt;. An example is &lt;code&gt;695980776.16554499&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;For most APIs, this default is not useful and usually results in a decoding error.
So in almost all real-world scenarios, we explicitly choose a decoding strategy.&lt;/p&gt;&lt;h3&gt;Decoding timestamps&lt;/h3&gt;&lt;p&gt;A common format is a numeric timestamp. If the backend sends seconds since 1970:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;createdAt&amp;quot;: 1698787200
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we can use:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the value is in milliseconds instead:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;createdAt&amp;quot;: 1698787200000
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we can switch to:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;decoder.dateDecodingStrategy = .millisecondsSince1970
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As long as the backend is consistent, these strategies are simple, fast, and reliable.&lt;/p&gt;&lt;h3&gt;ISO 8601 dates&lt;/h3&gt;&lt;p&gt;Another very common format is ISO 8601:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;createdAt&amp;quot;: &amp;quot;2023-11-01T12:00:00Z&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Swift provides a built-in strategy for this as well:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;decoder.dateDecodingStrategy = .iso8601
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This works for many APIs, but it’s worth noting that the built-in ISO 8601 parser is somewhat strict. Variations like fractional seconds or slightly different timezone representations may cause decoding to fail.
That’s often the moment where custom strategies become necessary.&lt;/p&gt;&lt;h3&gt;Using a custom date formatter&lt;/h3&gt;&lt;p&gt;When the date format doesn&amp;#x27;t match any built-in strategy, we can decode dates using a custom &lt;code&gt;DateFormatter&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;For example, given this JSON:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;createdAt&amp;quot;: &amp;quot;2023-11-01 14:30:00&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we can configure the decoder like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let formatter = DateFormatter()
formatter.dateFormat = &amp;quot;yyyy-MM-dd HH:mm:ss&amp;quot;
formatter.locale = Locale(identifier: &amp;quot;en_US_POSIX&amp;quot;)
formatter.timeZone = TimeZone(secondsFromGMT: 0)

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Setting the locale and time zone explicitly is important here. Without it, decoding can behave differently depending on the user&amp;#x27;s device settings.&lt;/p&gt;&lt;h3&gt;When one format is not enough&lt;/h3&gt;&lt;p&gt;Some APIs return different date formats in different fields, or even within the same field over time. In those cases, we can use a &lt;code&gt;.custom&lt;/code&gt; strategy:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;decoder.dateDecodingStrategy = .custom { decoder in
    let container = try decoder.singleValueContainer()
    let string = try container.decode(String.self)

    let formatters: [DateFormatter] = [
        isoFormatter,
        fallbackFormatter
    ]

    for formatter in formatters {
        if let date = formatter.date(from: string) {
            return date
        }
    }

    throw DecodingError.dataCorruptedError(
        in: container,
        debugDescription: &amp;quot;Invalid date format: \(string)&amp;quot;
    )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This keeps the complexity in one place and prevents needing to parse individual fields separately.&lt;/p&gt;</content:encoded><category>json</category><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding SwiftUI's Grid container]]></title><description><![CDATA[Learn how to arrange views in a two-dimensional layout.]]></description><link>https://tanaschita.com/swiftui-grid-container</link><guid isPermaLink="false">https://tanaschita.com/swiftui-grid-container</guid><pubDate>Sun, 21 Dec 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;Grid&lt;/code&gt; gives us a way to build two-dimensional layouts without using nested &lt;code&gt;HStack&lt;/code&gt;/&lt;code&gt;VStack&lt;/code&gt; combinations. The grid aligns items based on columns determined by the content we place inside rows.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how it works and when to prefer it over stack combinations.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;&lt;code&gt;Grid&lt;/code&gt; is a two-dimensional layout where:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Each &lt;code&gt;GridRow&lt;/code&gt; represents one horizontal row.&lt;/li&gt;&lt;li&gt;Each element inside a row creates or fills a column.&lt;/li&gt;&lt;li&gt;Columns align automatically across rows.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So if the first row contains three elements, and the second row contains only two, SwiftUI still reserves a third column, keeping everything aligned.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    Grid(horizontalSpacing: 20, verticalSpacing: 20) {
        ForEach(animals, id: \.id) { animal in
            GridRow(alignment: .top) {
                Text(animal.name)
                    .gridColumnAlignment(.trailing)
                Image(systemName: animal.imageSystemName)
                    .gridColumnAlignment(.center)
                Text(animal.description)
                    .gridColumnAlignment(.leading)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, each row is aligned with the &lt;code&gt;gridColumnAlignment&lt;/code&gt; modifier. This results in the following view:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4cd30f28238def1c5ef4daf2fe4a148a/78a90/example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:32.53333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAA+UlEQVQoz5WRy0rDUBCG+0JibTHQRTf6DApaF6Uuii+T5sF0YeIjuGgsuTSnuZ3bJ+cYwZWkAz8MDPMx/z+T+6dn7lYbr4f1lovpnCSJcaWVxhqLtQZrLdYYlFJ+FkURi+UNq82L33Ucp8lf4ON6y3QekMQxxkIrFP2pp8r3iOJAle0ps/Q84OXs2gNdGWPQWlEfM2Tf+r5t6vFAZ/lqHvD2+o5U0J0UqlHUVUYjSmpRIMrMA3dhyGJ5O8LyLCAeLpSdRktFIwoPFOUBccx/gLtwrOWAj+EpXaORbU9VpFR5iii+yNLP84Duwt8vG2sA67O0g6Ts/83wGxSckdAsltiJAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Grid container example.&quot; title=&quot;Grid container example.&quot; src=&quot;/static/4cd30f28238def1c5ef4daf2fe4a148a/2e9ed/example.png&quot; srcSet=&quot;/static/4cd30f28238def1c5ef4daf2fe4a148a/12d18/example.png 375w,/static/4cd30f28238def1c5ef4daf2fe4a148a/44507/example.png 750w,/static/4cd30f28238def1c5ef4daf2fe4a148a/2e9ed/example.png 1500w,/static/4cd30f28238def1c5ef4daf2fe4a148a/78a90/example.png 1710w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Grid container example.&lt;/div&gt;&lt;p&gt;So a grid is basically a structured &lt;code&gt;HStack&lt;/code&gt;/&lt;code&gt;VStack&lt;/code&gt; combination - with the important difference for the shared column alignment. So if we want column alignment across multiple rows without manual geometry, &lt;code&gt;Grid&lt;/code&gt; is the view to go.&lt;/p&gt;&lt;p&gt;&lt;code&gt;Grid&lt;/code&gt; also provides the possibility to let a cell span multiple columns using &lt;code&gt;gridCellColumns(_:)&lt;/code&gt;. This allows us to create layouts where certain elements such as section headers or wide images stretch across the full row while others remain aligned to their individual columns.&lt;/p&gt;&lt;p&gt;It is worth mentioning, that Grid does not automatically wrap elements to the next line. SwiftUI will place all items in a single row unless we manually create rows. The grid is column-aligned, not space-aware. SwiftUI currently doesn’t provide a built-in flowing grid container.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Adding text input actions to iOS push and local notifications]]></title><description><![CDATA[Learn how to let users reply directly inside a notification.]]></description><link>https://tanaschita.com/ios-notifications-textinput</link><guid isPermaLink="false">https://tanaschita.com/ios-notifications-textinput</guid><pubDate>Sun, 14 Dec 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Interactive notifications let users take action without opening the app. In addition to being able to &lt;a href=&quot;/ios-notifications-custom-actions/&quot;&gt;add custom actions to notifications&lt;/a&gt;, we also have the option to add a notification action that allows custom &lt;strong&gt;text input&lt;/strong&gt;. This is especially useful for quick replies, feedback, or short form responses without forcing users to open the app.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll walk through how to create text input actions, how to register the required categories, and how to handle the user&amp;#x27;s response in a SwiftUI app.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Defining a text input action&lt;/h3&gt;&lt;p&gt;A text input action is represented by a &lt;code&gt;UNTextInputNotificationAction&lt;/code&gt;. This action displays a text field inside the notification UI:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let howManyGlassesInputAction =  UNTextInputNotificationAction(
        identifier: NotificationAction.howManyGlasses.rawValue,
        title: &amp;quot;How many glasses of water did you drink?&amp;quot;,
        options: [],
        textInputButtonTitle: &amp;quot;Add&amp;quot;,
        textInputPlaceholder: &amp;quot;Number of glasses&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once defined, this action behaves just like any other notification action and needs to be registered inside a notification category.&lt;/p&gt;&lt;h3&gt;Creating a category&lt;/h3&gt;&lt;p&gt;Notification actions always belong to a notification category. Each delivered notification specifies which category it belongs to, and iOS uses that to decide which actions to display.&lt;/p&gt;&lt;p&gt;We can now create a category that includes our text input action:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let drinkingRemiderCategory = UNNotificationCategory(
    identifier: NotificationCategory.drinkingReminder.rawValue,
    actions: [doneAction, notThirstyAction, howManyGlassesInputAction],
    intentIdentifiers: [],
    options: .customDismissAction)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Registering a category&lt;/h3&gt;&lt;p&gt;To encapsulate all notification-related logic, we can create a dedicated &lt;code&gt;NotificationService&lt;/code&gt; class that is responsible for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Creating and registering notification categories&lt;/li&gt;&lt;li&gt;Handling text input responses&lt;/li&gt;&lt;li&gt;Requesting authorization (not part of this article)&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

class NotificationService: NSObject, UNUserNotificationCenterDelegate {

    func setup() {
        UNUserNotificationCenter.current().delegate = self
        registerCategories()
    }

    private func registerCategories() {
        // Create actions and categories

        // Register the category
        UNUserNotificationCenter.current().setNotificationCategories([drinkingReminderCategory])
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then use this service in a SwiftUI app as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct MyApp: App {

    private let notificationService = NotificationService()

    init() {
      notificationService.setup()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, every time a notification with the configured id is delivered, the text input action is shown to the user.&lt;/p&gt;&lt;h3&gt;Handling the response&lt;/h3&gt;&lt;p&gt;When a user chooses the text input action and provides an input, the text is included in the response object delivered to our app. To access it, we can map the response to the &lt;code&gt;UNTextInputNotificationResponse&lt;/code&gt; type that has the property &lt;code&gt;userText&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&amp;gt; Void) {
    switch response.actionIdentifier {
    case NotificationAction.howManyGlasses.rawValue:
        if let userInput = (response as? UNTextInputNotificationResponse)?.userText {
            // Handle text input
        }
    default:
        break
    }
    completionHandler()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This method also belongs in &lt;code&gt;NotificationService&lt;/code&gt;, keeping all notification handling in one focused place.&lt;/p&gt;</content:encoded><category>notifications</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to avoid retain cycles when working with tasks in Swift]]></title><description><![CDATA[Understanding memory management in Swift concurrency.]]></description><link>https://tanaschita.com/swift-async-tasks-memory-management</link><guid isPermaLink="false">https://tanaschita.com/swift-async-tasks-memory-management</guid><pubDate>Sun, 07 Dec 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s concurrency system gives us a clean, readable way to express asynchronous work using async/await and tasks. Since we are capturing values when working with tasks, it&amp;#x27;s important to understand how memory management works here to avoid potencial retain cycles.&lt;/p&gt;&lt;p&gt;In this article, we look at how tasks hold on to references and where retain cycles might appear.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Whenever we create a task, for example through &lt;code&gt;Task {}&lt;/code&gt;, Swift captures any values we reference inside the task body. These captures are strong by default.&lt;/p&gt;&lt;p&gt;This means:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A task keeps its captured values alive until the task completes.&lt;/li&gt;&lt;li&gt;If we capture &lt;code&gt;self&lt;/code&gt; strongly, and &lt;code&gt;self&lt;/code&gt; also holds a reference to the task, we create a retain cycle.&lt;/li&gt;&lt;li&gt;But importantly, this cycle lasts only for the lifetime of the task.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s look at a concrete example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class ImageLoader {
    private var task: Task&amp;lt;Void, Never&amp;gt;?

    func load() {
        task = Task {
            await loadImage()
        }
    }

    func loadImage() async {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What happens here is:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the &lt;code&gt;ImageLoader&lt;/code&gt; instance strongly retains the task (because task is a stored property)&lt;/li&gt;&lt;li&gt;the task strongly retains its closure,&lt;/li&gt;&lt;li&gt;the closure strongly retains &lt;code&gt;self&lt;/code&gt; (because we call &lt;code&gt;downloadImages()&lt;/code&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;So, while the task is running, we do have a retain cycle:&lt;/p&gt;&lt;p&gt;&lt;code&gt;ImageLoader&lt;/code&gt; → &lt;code&gt;Task&lt;/code&gt; → &lt;code&gt;ImageLoader&lt;/code&gt;&lt;/p&gt;&lt;p&gt;However, tasks have an important difference from classic stored completion handlers: a task releases its closure once the task finishes.&lt;/p&gt;&lt;p&gt;This means:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In this example, the cycle lasts only for as long as the image is loading.&lt;/li&gt;&lt;li&gt;When the task completes, it drops its closure and therefore releases &lt;code&gt;self&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If we prefer to avoid even this temporary cycle, we can capture self weakly with &lt;code&gt;[weak self]&lt;/code&gt; but for short-lived tasks it is usually optional.&lt;/p&gt;&lt;h3&gt;Where retain cycles become memory leaks&lt;/h3&gt;&lt;p&gt;Temporary retain cycles are rarely an issue, but with permanent ones we may run into memory leaks. This happens when all three are true:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We store the task (as a property).&lt;/li&gt;&lt;li&gt;The task&amp;#x27;s closure captures self strongly.&lt;/li&gt;&lt;li&gt;The task never finishes or finishes much later than we expect.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This can appear in long-running loops or stream processing:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class MessageListener {
    private var task: Task&amp;lt;Void, Never&amp;gt;?

    func start() {
        task = Task { [weak self] in
            for await message in await messages.stream() {
                guard let self else { return }
                self.handle(message)
            }
        }
    }

    func handle(_ message: Message) { }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the loop may run indefinitely. Without &lt;code&gt;[weak self]&lt;/code&gt;, the task keeps &lt;code&gt;self&lt;/code&gt; alive, and &lt;code&gt;self&lt;/code&gt; keeps the task alive, forming a classic retain cycle.&lt;/p&gt;&lt;p&gt;Using a weak capture breaks this cycle and ensures that the task naturally terminates once the listener goes out of scope.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding associated domains in iOS]]></title><description><![CDATA[Learn how to configure associated domains for universal links, shared web credentials or app clips.]]></description><link>https://tanaschita.com/ios-associated-domains-basics</link><guid isPermaLink="false">https://tanaschita.com/ios-associated-domains-basics</guid><pubDate>Sun, 30 Nov 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Associated domains create a secure, system-managed connection between an iOS app and its matching website, allowing us to support features like universal links, shared web credentials with password auto fill or app clips.&lt;/p&gt;&lt;p&gt;All of these rely on the same mechanism: an &lt;code&gt;apple-app-site-association&lt;/code&gt; file on the website + an associated domains entitlement in the app.&lt;/p&gt;&lt;p&gt;In this artile, we walk through how the system works and how to configure everything correctly.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What associated domains are&lt;/h3&gt;&lt;p&gt;In simple terms, associated domains allow iOS to verify: &amp;quot;This website and this app belong together.&amp;quot;&lt;/p&gt;&lt;p&gt;Once verified, we can implement features such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Universal Links&lt;/strong&gt;: let us route specific URLs directly into the app instead of opening them in a browser. If the app is installed, we can navigate the user to the appropriate screen immediately. If it is not installed, the URL opens on the website as usual.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Shared Web Credentials with Password AutoFill&lt;/strong&gt;: our app can access authentication information a user previously saved in iCloud Keychain while using the connected website. When a user has already signed in on the web, the app can request the username and password and fill them automatically&lt;/li&gt;&lt;li&gt;&lt;strong&gt;App Clips&lt;/strong&gt;: an app clip is a lightweight version of the iOS application offering users access to certain functionality. It lets users complete a quick task like renting a bike or ordering food - even before installing the full app.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Setting this up requires two parts:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A JSON file (&lt;code&gt;apple-app-site-association&lt;/code&gt;) hosted on the website&lt;/li&gt;&lt;li&gt;A matching entitlement inside the app&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Creating and configuring an associated domain file&lt;/h3&gt;&lt;p&gt;The first step requires creating a file named &lt;code&gt;apple-app-site-association&lt;/code&gt; without an extension and configuring it by using the JSON format. Its top-level structure typically contains keys for the services we want to support:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;applinks&amp;quot;: {},
  &amp;quot;webcredentials&amp;quot;: {},
  &amp;quot;appclips&amp;quot;: {}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A example configuration for universal links might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;applinks&amp;quot;: {
    &amp;quot;details&amp;quot;: [
      {
        &amp;quot;appIDs&amp;quot;: [&amp;quot;APPIDPREFIX.com.tanaschita.exampleApp&amp;quot;],
        &amp;quot;components&amp;quot;: [
          {
            &amp;quot;/&amp;quot;: &amp;quot;birds&amp;quot;
          }
        ]
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It tells iOS that any URL of this website whose path begins with &lt;code&gt;/birds&lt;/code&gt; should be handled by our app.&lt;/p&gt;&lt;h3&gt;Adding the associated domain file to the related website&lt;/h3&gt;&lt;p&gt;After configuring the association file the way we need, the second step is to place it into the website&amp;#x27;s &lt;code&gt;.well-known&lt;/code&gt; directory:&lt;/p&gt;&lt;p&gt;&lt;code&gt;https://&amp;lt;domain&amp;gt;/.well-known/apple-app-site-association&lt;/code&gt;&lt;/p&gt;&lt;p&gt;The file&amp;#x27;s URL should match the format shown above. The file:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;must be served over HTTPS&lt;/li&gt;&lt;li&gt;must not have a .json extension&lt;/li&gt;&lt;li&gt;should be served with a JSON content type, which Apple expects when parsing&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Adding an associated domain entitlement to the iOS app&lt;/h3&gt;&lt;p&gt;Next, we can add the entitlement to our app:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Open the app target in Xcode.&lt;/li&gt;&lt;li&gt;Navigate to Signing &amp;amp; Capabilities.&lt;/li&gt;&lt;li&gt;Add Associated Domains.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Inside the capability, we can add entries using this format:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;applinks:example.com
webcredentials:example.com
appclips:example.com
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Enabling alternate mode for testing&lt;/h3&gt;&lt;p&gt;Apps request &lt;code&gt;apple-app-site-association&lt;/code&gt; files from an Apple-managed content delivery network (CDN) instead directly from the web server. The CDN regularly connects to the web server to obtain the latest version.&lt;/p&gt;&lt;p&gt;During development, the web server might be not publicly reachable yet. To still be able to test, we can enable the alternate mode feature to bypass the CDN and connect directly to the server by adding the following configuration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;applinks:example.com?mode=developer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checkout this article &lt;a target=&quot;_blank&quot; href=&quot;/ios-associated-domains-alternate-mode/&quot;&gt;using associated domains alternate mode&lt;/a&gt; to learn more.&lt;/p&gt;</content:encoded><category>associated domains</category><category>security</category><category>ios</category></item><item><title><![CDATA[Using associated domains alternate mode during development]]></title><description><![CDATA[How to test AASA files without waiting for Apple's CDN.]]></description><link>https://tanaschita.com/ios-associated-domains-alternate-mode</link><guid isPermaLink="false">https://tanaschita.com/ios-associated-domains-alternate-mode</guid><pubDate>Sun, 23 Nov 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with associated domains, for example to support universal links, shared web credentials, or App Clips, iOS fetches the &lt;code&gt;apple-app-site-association&lt;/code&gt; (AASA) file through Apple&amp;#x27;s CDN, not directly from the server where the file is hosted.&lt;/p&gt;&lt;p&gt;This works well in production, but during development it can be inconvenient: changes to the file may take a while to propagate, and local or staging servers might not be publicly reachable at all.&lt;/p&gt;&lt;p&gt;To make development easier, Apple offers an alternate mode.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;In this mode, iOS bypasses the CDN and talks directly to our server. We can enable it by modifying the associated domains entries in Xcode.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s say we have the following associated domain configured:&lt;/p&gt;&lt;p&gt;&lt;code&gt;applinks:example.com&lt;/code&gt;&lt;/p&gt;&lt;p&gt;During development, we can add a mode parameter, for example:&lt;/p&gt;&lt;p&gt;&lt;code&gt;applinks:example.com?mode=developer&lt;/code&gt;&lt;/p&gt;&lt;p&gt;To use developer mode, the device must also opt in. On the device, we enable &lt;code&gt;Settings → Developer → Associated Domains Development&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Additionally, for developer mode to work, the app needs to be signed with a development provisioning profile.&lt;/p&gt;&lt;p&gt;Only with all of these in place (the &lt;code&gt;?mode=developer&lt;/code&gt; suffix in Xcode, the developer toggle on the device and signing with a development provisioning profile), changes to the AASA file become visible immediately, and we can even test against local or staging environments as long as they are reachable from the device.&lt;/p&gt;&lt;h3&gt;Available alternate modes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;developer&lt;/code&gt;: works on devices in developer mode and with apps signed using a development profile.&lt;/li&gt;&lt;li&gt;&lt;code&gt;managed&lt;/code&gt;: intended for enterprise environments on devices enrolled in Mobile Device Management (MDM). Useful when organizations control device policies.&lt;/li&gt;&lt;li&gt;&lt;code&gt;developer+managed&lt;/code&gt;: a combination of the two. Direct fetching is enabled only when the device is both in developer mode and managed by MDM.&lt;/li&gt;&lt;/ul&gt;</content:encoded><category>associated domains</category><category>ios</category></item><item><title><![CDATA[Bridging completion handlers to Swift's async/await]]></title><description><![CDATA[Learn how to adapt completion-based APIs to Swift's concurrency model using continuations.]]></description><link>https://tanaschita.com/swift-async-completions-to-async-await</link><guid isPermaLink="false">https://tanaschita.com/swift-async-completions-to-async-await</guid><pubDate>Sun, 16 Nov 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Before Swift introduced structured concurrency and async/await, most asynchronous APIs in iOS used completion handlers. While many system frameworks have since adopted native async functions, we still often encounter callback-based APIs.&lt;/p&gt;&lt;p&gt;To integrate those with Swift’s modern concurrency model, we can bridge them using &lt;strong&gt;continuations&lt;/strong&gt;. Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What continuations do&lt;/h3&gt;&lt;p&gt;Every time we call &lt;code&gt;await&lt;/code&gt;, the current code suspends by creating a continuation that captures the state at the point of suspension. When an awaited function completes, the captured state is recreated from the continuation and the original code resumes. This all happens behind the scenes when we use existing &lt;code&gt;async&lt;/code&gt; functions.&lt;/p&gt;&lt;p&gt;To create our own &lt;code&gt;async&lt;/code&gt; functions, we can use so called continuation functions. Continuation functions give us control over suspending and resuming a function.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s see how to do that.&lt;/p&gt;&lt;h3&gt;Using &lt;code&gt;withCheckedContinuation&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s look at a simple example. Suppose we have an API that loads data using a completion handler:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func load(completion: (Int) -&amp;gt; Void) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can bridge it to Swift concurrency like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func load() async -&amp;gt; Int {
    return await withCheckedContinuation({ continuation in
        load() { result in
            continuation.resume(returning: result)
        }
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we use Swift&amp;#x27;s &lt;code&gt;withCheckedContinuation&lt;/code&gt; function to suspend and then call &lt;code&gt;resume&lt;/code&gt; after we get a result from our completion handler function. Resuming from a continuation must happen exactly once.&lt;/p&gt;&lt;p&gt;Besides &lt;code&gt;withCheckedContinuation()&lt;/code&gt;, there are other continuation functions we can use:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;withCheckedThrowingContinuation()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;withUnsafeContinuation()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;withUnsafeThrowingContinuation()&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Using &lt;code&gt;withCheckedThrowingContinuation&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;If our callback reports errors, we can use the throwing version instead. For example, if we have:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func load(completion: (Result&amp;lt;Int, Error&amp;gt;) -&amp;gt; Void) {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can wrap it into an &lt;code&gt;async throws&lt;/code&gt; function as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func load() async throws -&amp;gt; Int {
    return try await withCheckedThrowingContinuation({ continuation in
        load() { result in
            switch result {
            case .success(let successResult):
                continuation.resume(returning: successResult)
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This lets us use the &lt;code&gt;try await&lt;/code&gt; syntax when calling the function.&lt;/p&gt;&lt;h3&gt;Using &lt;code&gt;withUnsafeContinuation()&lt;/code&gt; and &lt;code&gt;withUnsafeThrowingContinuation()&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Both functions, &lt;code&gt;withUnsafeContinuation()&lt;/code&gt; and &lt;code&gt;withUnsafeThrowingContinuation()&lt;/code&gt; behave the same as &lt;code&gt;withCheckedContinuation()&lt;/code&gt; and &lt;code&gt;withCheckedThrowingContinuation()&lt;/code&gt;. The only difference is that while checked continuations perform runtime checks for missing or multiple resume operations, unsafe continuations don&amp;#x27;t.&lt;/p&gt;&lt;p&gt;Checked continuations are generally safer and preferred for most cases.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Transforming AsyncStream with Swift Async Algorithms]]></title><description><![CDATA[Bringing Combine-like operators to Swift concurrency.]]></description><link>https://tanaschita.com/swift-async-algorithms-asyncstream</link><guid isPermaLink="false">https://tanaschita.com/swift-async-algorithms-asyncstream</guid><pubDate>Sun, 09 Nov 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we explored how we can use &lt;a target=&quot;_blank&quot; href=&quot;/swift-combine-subjects-vs-asyncstream/&quot;&gt;AsyncStream to implement reactive-style listening patterns&lt;/a&gt;, we also talked about one of its limitations: Swift&amp;#x27;s standard library doesn’t include many operators for transforming or combining asynchronous values.&lt;/p&gt;&lt;p&gt;That&amp;#x27;s where the Swift Async Algorithms package comes in. It extends the &lt;code&gt;AsyncSequence&lt;/code&gt; protocol with operators like &lt;code&gt;debounce&lt;/code&gt;, &lt;code&gt;throttle&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, and &lt;code&gt;combineLatest&lt;/code&gt;, bringing Combine-like expressiveness to Swift concurrency.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how this works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Why it&amp;#x27;s not part of the Swift standard library (yet)&lt;/h3&gt;&lt;p&gt;The Swift Async Algorithms package is developed and maintained by Apple as part of the official Swift open-source project: &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/apple/swift-async-algorithms&quot;&gt;&lt;a href=&quot;https://github.com/apple/swift-async-algorithms&quot;&gt;https://github.com/apple/swift-async-algorithms&lt;/a&gt;&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Apple uses these standalone &amp;quot;incubation&amp;quot; packages to evolve and stabilize new APIs before locking them into the language. Once a package&amp;#x27;s design has matured, it may eventually become part of Swift itself.&lt;/p&gt;&lt;p&gt;So to use it, we currently need to add it to our project, for example via Swift Package Manager.&lt;/p&gt;&lt;h3&gt;Useful operators the package provides&lt;/h3&gt;&lt;p&gt;The Swift Async Algorithms package includes many familiar operators from the reactive programming world, built to fit into Swift&amp;#x27;s async/await model. Here are some examples:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;debounce(for:clock:)&lt;/code&gt; - delays emissions until a pause in values&lt;/li&gt;&lt;li&gt;&lt;code&gt;throttle(for:clock:)&lt;/code&gt; - limits how often new values are emitted&lt;/li&gt;&lt;li&gt;&lt;code&gt;merge(::)&lt;/code&gt; - merges multiple async sequences into one&lt;/li&gt;&lt;li&gt;&lt;code&gt;combineLatest(::)&lt;/code&gt; - emits pairs of the latest values from two sequences&lt;/li&gt;&lt;li&gt;&lt;code&gt;zip(::)&lt;/code&gt; – pairs values from two sequences positionally&lt;/li&gt;&lt;li&gt;&lt;code&gt;buffer(policy:)&lt;/code&gt; - controls how to handle slow consumers&lt;/li&gt;&lt;li&gt;&lt;code&gt;chain(::)&lt;/code&gt; - sequences two async sequences one after another&lt;/li&gt;&lt;li&gt;&lt;code&gt;latest(::)&lt;/code&gt; - keeps only the most recent value from each source&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Because &lt;code&gt;AsyncStream&lt;/code&gt; conforms to &lt;code&gt;AsyncSequence&lt;/code&gt;, all operators in Swift Async Algorithms automatically work with it.&lt;/p&gt;&lt;h3&gt;Example: Debouncing user input&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s look at a common use case: debouncing text input to avoid triggering too many requests while a user types:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;searchTask = Task {
    for await query in await searchInput.stream()
        .debounce(for: .seconds(0.5), clock: .continuous)
        .removeDuplicates()
    {
        await performSearch(for: query)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;searchInput&lt;/code&gt; is the &lt;code&gt;AsyncPassthrough&lt;/code&gt; we have built in &lt;a target=&quot;_blank&quot; href=&quot;/swift-combine-subjects-vs-asyncstream/&quot;&gt;this article&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;With this setup, when the user types into a search field, &lt;code&gt;performSearch&lt;/code&gt; only fires once the user stops typing for half a second. This pattern mirrors Combine&amp;#x27;s &lt;code&gt;.debounce&lt;/code&gt; operator.&lt;/p&gt;&lt;h3&gt;Example: Combining streams&lt;/h3&gt;&lt;p&gt;Another area where reactive programming shines is combining multiple streams. Let&amp;#x27;s see how the &lt;code&gt;merge&lt;/code&gt; operator can be applied:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import AsyncAlgorithms

let numbers = AsyncStream&amp;lt;Int&amp;gt; { continuation in
    Task {
        for i in 1...3 {
            continuation.yield(i)
            try await Task.sleep(for: .milliseconds(300))
        }
        continuation.finish()
    }
}

let letters = AsyncStream&amp;lt;String&amp;gt; { continuation in
    Task {
        for char in [&amp;quot;A&amp;quot;, &amp;quot;B&amp;quot;, &amp;quot;C&amp;quot;] {
            continuation.yield(char)
            try await Task.sleep(for: .milliseconds(500))
        }
        continuation.finish()
    }
}

Task {
    for await value in merge(numbers, letters) {
        print(&amp;quot;Received:&amp;quot;, value)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The result is an interleaved async sequence that emits values as they arrive.&lt;/p&gt;&lt;h3&gt;Beyond operators: AsyncChannel&lt;/h3&gt;&lt;p&gt;In addition to operators, the package also provides new async types such as &lt;code&gt;AsyncChannel&lt;/code&gt;. We&amp;#x27;ll explore it in a follow-up article.&lt;/p&gt;</content:encoded><category>async/await</category><category>reactive programming</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding Task and Task.detached in Swift concurrency]]></title><description><![CDATA[Learn the what the differences are between Task and Task.detached in Swift.]]></description><link>https://tanaschita.com/swift-async-task-vs-task-detached</link><guid isPermaLink="false">https://tanaschita.com/swift-async-task-vs-task-detached</guid><pubDate>Sat, 01 Nov 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we explored &lt;a target=&quot;_blank&quot; href=&quot;/swift-async-tasks-cancellation/&quot;&gt;structured and unstructured tasks&lt;/a&gt;, we saw a small example of using &lt;code&gt;Task {}&lt;/code&gt; vs. &lt;code&gt;Task.detached {}&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll go a bit deeper and look at how the two differ, and when it makes sense to use each.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Staying in context with Task&lt;/h3&gt;&lt;p&gt;When we create a regular &lt;code&gt;Task&lt;/code&gt;, it inherits context from where it&amp;#x27;s started.&lt;/p&gt;&lt;p&gt;That means:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;If we’re inside an actor, the new task runs on that actor.&lt;/li&gt;&lt;li&gt;If the parent task has a certain priority or is cancelled, that state is inherited.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor DataService {
    func refresh() {
        Task {
            await updateCache()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;updateCache()&lt;/code&gt; runs inside the actor. We don&amp;#x27;t need to think about isolation or thread safety, Swift takes care of that.&lt;/p&gt;&lt;h3&gt;Creating an independent task with Task.detached&lt;/h3&gt;&lt;p&gt;&lt;code&gt;Task.detached&lt;/code&gt; starts a completely independent task. It doesn&amp;#x27;t inherit any context: not actor isolation, not priority, not cancellation.&lt;/p&gt;&lt;p&gt;In some cases, we can offload work from an actor so that it stays responsive. Actors process one piece of work at a time, so if we want to perform longer operations that don&amp;#x27;t depend on the actor&amp;#x27;s state, we can run them outside the actor&amp;#x27;s executor:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor UserService {
    private let secureStore: SecureStore

    func clear() {
        Task.detached { [secureStore] in
            await secureStore.clearTokens()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, the actor becomes available immediately after starting the detached task.&lt;/p&gt;&lt;p&gt;However, this only works if &lt;code&gt;secureStore&lt;/code&gt; is safe to call from outside the actor, for example, if they are actors themselves or thread-safe.&lt;/p&gt;&lt;h3&gt;When detaching doesn&amp;#x27;t help&lt;/h3&gt;&lt;p&gt;The example above works because the detached task doesn&amp;#x27;t touch the actor&amp;#x27;s state. But if we try to access it, we lose the benefit:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor DataService {
    func refresh() {
        Task.detached { [weak self] in
            guard let self else { return }
            await self.updateCache()
        }
    }

    private func updateCache() async { ... }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This compiles, but it&amp;#x27;s effectively the same as using a regular Task. The detached task runs independently, then immediately hops back into the actor to do its work. So there&amp;#x27;s no real advantage here, we could have used a regular &lt;code&gt;Task&lt;/code&gt; instead.&lt;/p&gt;&lt;p&gt;Also, it&amp;#x27;s a common misconception that &lt;code&gt;Task.detached&lt;/code&gt; is necessary to make a task &amp;quot;keep running&amp;quot;. That&amp;#x27;s not the case. Once a normal task is created outside structured concurrency, it already runs independently. A detached task doesn’t change that behavior. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class DataService {
    func startWork() {
        Task {
            await doLongRunningWork()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This task will continue until it completes or is cancelled explicitly. Replacing it with &lt;code&gt;Task.detached&lt;/code&gt; changes nothing in practice.&lt;/p&gt;&lt;p&gt;So if our goal is simply to start background work that finishes on its own, a normal &lt;code&gt;Task&lt;/code&gt; is usually enough.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding task cancellation and lifetimes in Swift concurrency]]></title><description><![CDATA[Learn how structured and unstructured tasks behave in Swift's concurrency system.]]></description><link>https://tanaschita.com/swift-async-tasks-cancellation</link><guid isPermaLink="false">https://tanaschita.com/swift-async-tasks-cancellation</guid><pubDate>Sun, 26 Oct 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s concurrency system gives us different ways to create and manage asynchronous work: some tasks are automatically tied to a scope, while others run independently. This distinction determines how long a task lives, whether it&amp;#x27;s automatically cancelled, and who is responsible for cleaning it up.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how cancellation works in structured concurrency, what changes when we move to unstructured or detached tasks, and how to manage task lifetimes safely.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Structured tasks&lt;/h3&gt;&lt;p&gt;In Swift, structured concurrency ties the lifetime of async work to the scope where it was created.&lt;/p&gt;&lt;p&gt;When we, for example, use &lt;code&gt;async let&lt;/code&gt;, &lt;code&gt;withTaskGroup&lt;/code&gt;, or SwiftUI&amp;#x27;s &lt;code&gt;.task&lt;/code&gt; modifier, the system automatically cancels work when that scope ends.&lt;/p&gt;&lt;p&gt;For example, a &lt;code&gt;Task&lt;/code&gt; started by SwiftUI through &lt;code&gt;.task { ... }&lt;/code&gt; is automatically cancelled when the view disappears:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var text = &amp;quot;&amp;quot;

    var body: some View {
        Text(text)
            .task {
                text = await loadTitle()
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the view leaves the hierarchy, SwiftUI cancels the task.&lt;/p&gt;&lt;p&gt;The same applies to &lt;code&gt;async let&lt;/code&gt; or &lt;code&gt;withTaskGroup&lt;/code&gt; inside an async function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func load() async throws -&amp;gt; [String] {
    async let a = loadUser()
    async let b = loadPosts()
    async let c = loadSettings()

    return try await [a, b, c]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, all three child tasks are bound to the scope of &lt;code&gt;load()&lt;/code&gt;. When the &lt;code&gt;load()&lt;/code&gt; function finishes, either successfully or with an error, or gets cancelled, all three async child tasks are cancelled as well.&lt;/p&gt;&lt;p&gt;This automatic cleanup is what makes structured concurrency predictable.&lt;/p&gt;&lt;h3&gt;Unstructured and detached tasks&lt;/h3&gt;&lt;p&gt;When we manually create a task using &lt;code&gt;Task { ... }&lt;/code&gt; outside of a structured scope (for example, in a non-async method), the lifetime is no longer managed for us. The task runs independently until it completes, or until we explicitly cancel it.&lt;/p&gt;&lt;p&gt;To be able to cancel a task, we can store it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class DataService {
    private var task: Task&amp;lt;Void, Never&amp;gt;?

    func startWork() {
        task = Task {
            await loadData()
        }
    }

    func cancelWork() {
        task?.cancel()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;An important thing to note here is that calling &lt;code&gt;cancel()&lt;/code&gt; doesn&amp;#x27;t immediately stop the task, it only sets a cancellation flag that async operations can check. Swift does not forcibly terminate running work. Instead, async calls and loops can choose when to respect that flag.&lt;/p&gt;&lt;p&gt;Some system functions, such as &lt;code&gt;URLSession.shared.data(from:)&lt;/code&gt; or &lt;code&gt;Task.sleep(for:)&lt;/code&gt;, automatically check for cancellation and throw a &lt;code&gt;CancellationError&lt;/code&gt;. For our own async code, we can check manually using &lt;code&gt;Task.checkCancellation()&lt;/code&gt; or &lt;code&gt;Task.isCancelled&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Swift also provides a second kind of unstructured task: &lt;code&gt;Task.detached&lt;/code&gt;. While a regular &lt;code&gt;Task { ... }&lt;/code&gt; inherits the caller&amp;#x27;s priority, actor context, or cancellation state (if any), a detached task runs completely independently. In our example above, using &lt;code&gt;Task.detached&lt;/code&gt; would behave the same in practice. But in other contexts, for instance when working inside actors or structured scopes, these differences can lead to noticeably different behavior. We&amp;#x27;ll look at the nuances between &lt;code&gt;Task&lt;/code&gt; and &lt;code&gt;Task.detached&lt;/code&gt; in an upcoming article.&lt;/p&gt;&lt;p&gt;Not every unstructured or detached task needs to be cancelled manually. If the task is doing something finite, for example performing a single network request, it will just run to completion.&lt;/p&gt;&lt;h3&gt;Long-running tasks&lt;/h3&gt;&lt;p&gt;Where manual cancellation becomes important is with long-lived work. In those cases, the task doesn&amp;#x27;t naturally end on its own.&lt;/p&gt;&lt;p&gt;For example, when working with &lt;code&gt;AsyncStream&lt;/code&gt;, we may have code that listens to updates as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Task {
    for await update in await service.updates {
        print(&amp;quot;Received:&amp;quot;, update)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This task will keep running indefinitely, until either the stream finishes or the task is cancelled. In cases like this, it&amp;#x27;s a good idea to either use a structured task or store the task in a property to be able to stop it when needed.&lt;/p&gt;&lt;p&gt;It&amp;#x27;s also good to know how &lt;code&gt;AsyncStream&lt;/code&gt; behaves on cancellation: when the task is cancelled, the consuming side of the stream stops receiving values because &lt;code&gt;AsyncStream&lt;/code&gt; internally checks for cancellation and drops any values sent to a terminated continuation. However, the producing side (the code yielding values into the stream) doesn&amp;#x27;t automatically stop. It will keep running unless we handle cleanup explicitly.&lt;/p&gt;&lt;p&gt;When working with long-lived tasks like streaming updates, it&amp;#x27;s important to decide where a task&amp;#x27;s lifetime should end: either automatically through structure, or explicitly through stored references and manual cancellation.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Comparing Combine's subjects with AsyncStream in Swift]]></title><description><![CDATA[Exploring how Swift concurrency can handle event streams compared with Combine.]]></description><link>https://tanaschita.com/swift-combine-subjects-vs-asyncstream</link><guid isPermaLink="false">https://tanaschita.com/swift-combine-subjects-vs-asyncstream</guid><pubDate>Sun, 19 Oct 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Combine&amp;#x27;s subjects such as &lt;code&gt;PassthroughSubject&lt;/code&gt; or &lt;code&gt;CurrentValueSubject&lt;/code&gt; have long been the go-to way to implement reactive observer patterns across an app. They provide a clear way to publish and observe values across different layers of an architecture.&lt;/p&gt;&lt;p&gt;With Swift concurrency, we now have another way to represent values that arrive over time: &lt;code&gt;AsyncStream&lt;/code&gt;. In this article, we&amp;#x27;ll look at how it works, how it compares to Combine, and when it makes sense to switch.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The Combine way&lt;/h3&gt;&lt;p&gt;A &lt;code&gt;PassthroughSubject&lt;/code&gt; is a simple Combine publisher. It emits values to all current subscribers, often used to send updates, for example from a service:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;final class DataService {
    let values = PassthroughSubject&amp;lt;String, Never&amp;gt;()

    func load() {
        someAsyncCall() { [weak self] result in
            self?.didUpdate.send(&amp;quot;New data loaded \(result)&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To observe updates, we subscribe using Combine’s &lt;code&gt;.sink&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class DataViewModel {
    private var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()
    private let service = DataService()

    init() {
        service.values
            .receive(on: DispatchQueue.main)
            .sink { [weak self] value in
                // received a new value
            }
            .store(in: &amp;amp;cancellables)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This setup works great in apps already built around Combine. The framework takes care of threading and synchronization for us, automatically delivering updates on the main queue when needed. Each subscription is cancellable through &lt;code&gt;AnyCancellable&lt;/code&gt;, making cleanup predictable and safe. Combine also provides a rich set of operators like &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;debounce&lt;/code&gt;, and &lt;code&gt;combineLatest&lt;/code&gt;, allowing us to transform, coordinate, and throttle asynchronous values with minimal code. And through SwiftUI&amp;#x27;s &lt;code&gt;.onReceive&lt;/code&gt;, Combine integrates naturally into the view layer.&lt;/p&gt;&lt;h3&gt;The concurrency-native alternative: AsyncStream&lt;/h3&gt;&lt;p&gt;&lt;code&gt;AsyncStream&lt;/code&gt; provides a lightweight, concurrency-native way to emit values over time. It fits naturally into Swift&amp;#x27;s structured concurrency model and can be used as an alternative to Combine&amp;#x27;s subjects in systems which does not need the full reactive possibilities Combine provides.&lt;/p&gt;&lt;p&gt;When using &lt;code&gt;AsyncStream&lt;/code&gt;, we need to be aware of that it does not handle thread safety or subscription management automatically, and cancellation behaves differently than in Combine. Used with care, it can be an elegant replacement for simple &amp;quot;publish and listen&amp;quot; flows.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at a simple example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor DataService {
    private var continuations: [UUID: AsyncStream&amp;lt;String&amp;gt;.Continuation] = [:]

    var updates: AsyncStream&amp;lt;String&amp;gt; {
        let id = UUID()
        return AsyncStream { continuation in
            continuations[id] = continuation
            continuation.onTermination = { [weak self] _ in
                Task { await self?.removeContinuation(id) }
            }
        }
    }

    func load() async {
        let result = await someAsyncCall()
        for continuation in continuations.values {
            continuation.yield(&amp;quot;New data loaded \(result)&amp;quot;)
        }
    }

    private func removeContinuation(_ id: UUID) {
        continuations[id] = nil
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To consume values, we can use &lt;code&gt;for await&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let service = DataService()

Task {
    for await update in service.updates {
        print(&amp;quot;Received:&amp;quot;, update)
    }
}

Task {
    await service.load()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s unpack what&amp;#x27;s happening:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;When we create an &lt;code&gt;AsyncStream&lt;/code&gt; we pass in a closure which gets called &lt;strong&gt;once per consumer&lt;/strong&gt;. Inside that closure, Swift gives us an &lt;code&gt;AsyncStream.Continuation&lt;/code&gt;. We can think of that continuation as a pipe into the consumer&amp;#x27;s &lt;code&gt;for await&lt;/code&gt; loop. We can send (yield) values into it, and the consumer will receive them one by one.&lt;/li&gt;&lt;li&gt;Since we can have multiple consumers, we save these continuations in an array: one continuation per active subscriber.&lt;/li&gt;&lt;li&gt;When we later call &lt;code&gt;yield&lt;/code&gt; we&amp;#x27;re broadcasting the new value to every consumer who’s currently listening. Each continuation represents an active &lt;code&gt;for await&lt;/code&gt; loop somewhere in the app.&lt;/li&gt;&lt;li&gt;If one of those subscribers stops consuming the stream (for example, its task is cancelled), the stream’s &lt;code&gt;onTermination&lt;/code&gt; closure is called. That&amp;#x27;s when we remove that continuation from the array. That keeps our list clean and prevents sending values into closed streams.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We use an &lt;strong&gt;actor&lt;/strong&gt; here because actors serialize access to shared mutable state. Without it, accessing the continuations collection from multiple concurrent tasks could lead to race conditions.&lt;/p&gt;&lt;h3&gt;Reusing AsyncStream&lt;/h3&gt;&lt;p&gt;If we find ourselves creating many small streams, we can wrap this pattern in a lightweight helper, similar to a &lt;code&gt;PassthroughSubject&lt;/code&gt;, but based on &lt;code&gt;AsyncStream&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor AsyncPassthrough&amp;lt;Value&amp;gt; {
    private var continuations: [UUID: AsyncStream&amp;lt;Value&amp;gt;.Continuation] = [:]

    func stream() -&amp;gt; AsyncStream&amp;lt;Value&amp;gt; {
        let id = UUID()
        return AsyncStream { continuation in
            continuations[id] = continuation
            continuation.onTermination = { [weak self] _ in
                Task { await self?.removeContinuation(id) }
            }
        }
    }

    func send(_ value: Value) {
        for continuation in continuations.values {
            continuation.yield(value)
        }
    }

    private func removeContinuation(_ id: UUID) {
        continuations[id] = nil
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Usage can look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let messages = AsyncPassthrough&amp;lt;String&amp;gt;()

Task {
    for await msg in await messages.stream() {
        print(&amp;quot;Received:&amp;quot;, msg)
    }
}

Task {
    await messages.send(&amp;quot;Hello&amp;quot;)
    await messages.send(&amp;quot;World&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;What about CurrentValueSubject?&lt;/h3&gt;&lt;p&gt;If we need the behaviour of a &lt;code&gt;CurrentValueSubject&lt;/code&gt; to hold and publish the latest value, we could build something similar as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor CurrentValue&amp;lt;Value&amp;gt; {
    private var value: Value
    private let passthrough = AsyncPassthrough&amp;lt;Value&amp;gt;()

    init(_ initial: Value) { value = initial }

    func updates() async -&amp;gt; AsyncStream&amp;lt;Value&amp;gt; {
        await passthrough.stream()
    }

    func set(_ newValue: Value) async {
        value = newValue
        await passthrough.send(newValue)
    }

    func get() -&amp;gt; Value { value }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we store the latest value in a property and expose a stream that emits it whenever it changes. This pattern gives us both current state (&lt;code&gt;get()&lt;/code&gt;) and an async sequence of updates (&lt;code&gt;updates&lt;/code&gt;).&lt;/p&gt;&lt;h3&gt;Using AsyncSequence operators&lt;/h3&gt;&lt;p&gt;Since &lt;code&gt;AsyncStream&lt;/code&gt; conforms to &lt;code&gt;AsyncSequence&lt;/code&gt;, we can also use the same sequence operators Swift provides, such as &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt; or &lt;code&gt;prefix&lt;/code&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Task {
    for await update in await service.updates
        .filter { !$0.isEmpty }
        .map { $0.uppercased() }
        .prefix(3)
    {
        print(&amp;quot;Received:&amp;quot;, update)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This applies basic transformations directly to the stream: we filter out empty updates, convert them to uppercase, and only take the first three results.&lt;/p&gt;&lt;p&gt;While these basic operators cover many common cases, they don’t replace the depth of Combine’s reactive toolset. Features like &lt;code&gt;debounce&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, or &lt;code&gt;combineLatest&lt;/code&gt; are often essential when coordinating multiple streams or handling user-driven events. In those scenarios, Combine still provides a richer and more expressive way to work with asynchronous data flows.&lt;/p&gt;&lt;h3&gt;Note on cancellation&lt;/h3&gt;&lt;p&gt;When a task consuming the stream is cancelled, Swift automatically calls the continuation&amp;#x27;s &lt;code&gt;onTermination&lt;/code&gt; handler. This means cancellation happens for free in many structured contexts, such as SwiftUI&amp;#x27;s &lt;code&gt;.task&lt;/code&gt; modifier. However, when we manually create tasks outside of SwiftUI, we need to manage cancellation ourselves.&lt;/p&gt;&lt;p&gt;We&amp;#x27;ll explore this distinction in an upcoming article.&lt;/p&gt;&lt;h3&gt;Wrapping up&lt;/h3&gt;&lt;p&gt;&lt;code&gt;AsyncStream&lt;/code&gt; doesn&amp;#x27;t replace Combine. For complex reactive data flows, Combine currently remains the more powerful framework. For lightweight &amp;quot;publish and listen&amp;quot; patterns, &lt;code&gt;AsyncStream&lt;/code&gt; can be used as an alternative.&lt;/p&gt;</content:encoded><category>async/await</category><category>combine</category><category>reactive programming</category><category>architecture</category><category>swift</category></item><item><title><![CDATA[When SwiftUI automatically applies the glass look and when it doesn’t]]></title><description><![CDATA[Understanding where SwiftUI decides to use the new glass styles automatically.]]></description><link>https://tanaschita.com/swiftui-glass-auto-apply</link><guid isPermaLink="false">https://tanaschita.com/swiftui-glass-auto-apply</guid><pubDate>Sun, 12 Oct 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since iOS 18, buttons, toolbars, and other controls can appear in Apple&amp;#x27;s new glass button style. What&amp;#x27;s interesting is that SwiftUI sometimes applies this effect automatically, depending on where a view is placed.
In this article, we&amp;#x27;ll look at when that happens, when it doesn&amp;#x27;t.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Automatic glass style in toolbars&lt;/h3&gt;&lt;p&gt;The most visible case of automatic glass styling happens in &lt;strong&gt;toolbars&lt;/strong&gt;. When we use system placements like &lt;code&gt;.confirmationAction&lt;/code&gt;, &lt;code&gt;.cancellationAction&lt;/code&gt;, or &lt;code&gt;.automatic&lt;/code&gt;, SwiftUI automatically applies the new &lt;code&gt;.glass&lt;/code&gt; style to buttons placed there.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItem(placement: .confirmationAction) {
        Button(&amp;quot;Save&amp;quot;, action: save)
    }
    ToolbarItem(placement: .cancellationAction) {
        Button(&amp;quot;Cancel&amp;quot;, action: cancel)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though we never set &lt;code&gt;.buttonStyle(.glass)&lt;/code&gt;, both buttons adopt the glass appearance.&lt;/p&gt;&lt;p&gt;At the moment, there’s no official way to opt out. Applying &lt;code&gt;.buttonStyle(.plain)&lt;/code&gt; or changing the layout does not remove the glass styling.&lt;/p&gt;&lt;h3&gt;When glass doesn&amp;#x27;t appear in toolbars&lt;/h3&gt;&lt;p&gt;The only remaining place in toolbars where the automatic glass style does not apply is the &lt;code&gt;.principal&lt;/code&gt; placement.
This slot is meant for titles or custom content and keeps its regular appearance, even if it contains buttons:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItem(placement: .principal) {
        HStack {
            Text(&amp;quot;Title Text&amp;quot;)
            Image(systemName: &amp;quot;leaf&amp;quot;)
            Button(&amp;quot;Action&amp;quot;, action: {})
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Buttons outside toolbars&lt;/h3&gt;&lt;p&gt;Outside toolbars, buttons do not get the glass style automatically. A standard &lt;code&gt;Button&lt;/code&gt; in a normal view does not adopt any automatic glass styling, we have to apply it ourselves if we want the look:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack(spacing: 20) {
    Button(&amp;quot;Add&amp;quot;, action: add)
    Button(&amp;quot;Glass Add&amp;quot;, action: add)
        .buttonStyle(.glass)
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to add custom actions to iOS push and local notifications in SwiftUI]]></title><description><![CDATA[Learn how to let users respond to notifications without opening the app.]]></description><link>https://tanaschita.com/ios-notifications-custom-actions</link><guid isPermaLink="false">https://tanaschita.com/ios-notifications-custom-actions</guid><pubDate>Sun, 05 Oct 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;By default, an iOS notification just shows an alert, and tapping it opens the app. But we can also make notifications interactive by adding custom actions, for example, letting users mark a task as done, snooze a reminder, or open the app directly from the notification.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to create, register, and handle these actions using SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What are notification actions?&lt;/h3&gt;&lt;p&gt;Notification actions are the buttons that appear below a notification&amp;#x27;s text. When a users taps on an action, depending on how we configure the action, the app can either run code silently in the background or open into the foreground if the user should see something. This makes actions perfect for lightweight interactions like &amp;quot;Mark as done&amp;quot; or &amp;quot;Later&amp;quot;, without disrupting the user.&lt;/p&gt;&lt;p&gt;To add custom actions to a notification, we need to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Define and register custom actions &amp;amp; categories at launch&lt;/li&gt;&lt;li&gt;Request notification permission (alert, sound, badge)&lt;/li&gt;&lt;li&gt;Implement the delegate method to handle the user’s chosen action&lt;/li&gt;&lt;li&gt;Assign category identifiers when scheduling local or sending remote notifications&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s go through these steps.&lt;/p&gt;&lt;h3&gt;Defining and registering categories and actions&lt;/h3&gt;&lt;p&gt;Each notification action is represented by &lt;code&gt;UNNotificationAction&lt;/code&gt; (or &lt;code&gt;UNTextInputNotificationAction&lt;/code&gt; for inline replies). Actions are grouped into categories, represented by &lt;code&gt;UNNotificationCategory&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s how we can define and register actions:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class NotificationsService {

    init() {
        registerActions()
    }

    private func registerActions() {
        let doneAction = UNNotificationAction(
            identifier: NotificationAction.done.rawValue,
            title: &amp;quot;Done&amp;quot;, options: [])

        let notThirstyAction = UNNotificationAction(
            identifier: NotificationAction.notThirsty.rawValue,
            title: &amp;quot;Not thirsty&amp;quot;, options: [])

        let category = UNNotificationCategory(
            identifier: NotificationCategory.drinkingReminder.rawValue,
            actions: [doneAction, notThirstyAction], intentIdentifiers: [], options: .customDismissAction)

        UNUserNotificationCenter.current().setNotificationCategories([category])
    }
}

enum NotificationCategory: String {
    case drinkingReminder
}

enum NotificationAction: String {
    case done
    case notThirsty
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we do want the app to open when a button is tapped, we can add the &lt;code&gt;.foreground&lt;/code&gt; option when defining the action.&lt;/p&gt;&lt;h3&gt;Asking for notification permission&lt;/h3&gt;&lt;p&gt;At app level, we create our service and ask the user for notification permission:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct NotificationActionsApp: App {
    private var notificationsService = NotificationsService()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    notificationsService.requestAuthorization()
                }
        }
    }
}

class NotificationsService {
    ...

    func requestAuthorization() {
        UNUserNotificationCenter.current()
            .requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
                print(&amp;quot;Notifications authorized: \(granted)&amp;quot;)
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Handling responses&lt;/h3&gt;&lt;p&gt;When the user taps an action, the system launches our app (in the background if needed) and calls the delegate.&lt;/p&gt;&lt;p&gt;To keep all notification handling centralized, we can register our service to be the delegate:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class NotificationsService: NSObject, UNUserNotificationCenterDelegate {
    override init() {
        super.init()
        registerActions()
        UNUserNotificationCenter.current().delegate = self
    }

    ...

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -&amp;gt; Void
    ) {
        switch response.actionIdentifier {
        case NotificationAction.done.rawValue:
            print(&amp;quot;User tapped Done&amp;quot;)
        case NotificationAction.notThirsty.rawValue:
            print(&amp;quot;User tapped Not thirsty&amp;quot;)
        default:
            break
        }
        completionHandler()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Attaching the category to a notification&lt;/h3&gt;&lt;p&gt;Now we can use the actions in a notification. To use it on a &lt;strong&gt;local notification&lt;/strong&gt;, we set the &lt;code&gt;categoryIdentifier&lt;/code&gt; on &lt;code&gt;UNMutableNotificationContent&lt;/code&gt; to tell the system which set of actions to display for that notification:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let content = UNMutableNotificationContent()
content.title = &amp;quot;Stay hydrated&amp;quot;
content.body = &amp;quot;It&amp;#x27;s time for a glass of water&amp;quot;
content.sound = .default
content.categoryIdentifier = NotificationCategory.drinkingReminder.rawValue
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the notification is delivered, iOS looks up that category and automatically shows the corresponding action buttons.&lt;/p&gt;&lt;p&gt;We can schedule the notification as usual:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
try await UNUserNotificationCenter.current().add(request)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use the category on a &lt;strong&gt;remote notification&lt;/strong&gt;, the server can include the identifier in the payload.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;aps&amp;quot;: {
    &amp;quot;alert&amp;quot;: {
      &amp;quot;title&amp;quot;: &amp;quot;Stay hydrated&amp;quot;,
      &amp;quot;body&amp;quot;: &amp;quot;It&amp;#x27;s time for a glass of water&amp;quot;
    },
    &amp;quot;sound&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;category&amp;quot;: &amp;quot;drinkingReminderCategory&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>notifications</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Structuring universal links in iOS]]></title><description><![CDATA[A practical way to parse and route universal links using enums and type-safe navigation.]]></description><link>https://tanaschita.com/swiftui-navigation-universal-links-architecture</link><guid isPermaLink="false">https://tanaschita.com/swiftui-navigation-universal-links-architecture</guid><pubDate>Sun, 28 Sep 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Universal links are a powerful way to create experiences between a website and an iOS app. As an app grows and supports more types of universal links, managing them can become complex without proper structure.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at a simple architecture that keeps universal link handling clean, testable, and easy to extend. Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Handling a universal link usually means parsing the incoming URL, extracting parameters and routing to the right part of the app. To represent a universal link, we can use an enum which acts as the source of truth for every universal link our app understands:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UniversalLink {
    /// Example: https://www.example.com/mobile-app/recipes
    /// Switches to the recipes tab.
    case recipes

    /// Example: https://www.example.com/mobile-app/recipe?id=64ba8b48a92d44eb4e0ef89e
    /// Opens a specific recipe.
    case recipe(id: String)

    /// Example: https://www.example.com/mobile-app/profile?to=settings
    /// Opens a detail view inside the profile tab.
    case profile(ProfileUniversalLinkDestination)
}

enum ProfileUniversalLinkDestination: String {
    case settings
    case favorites
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This keeps the list of supported links visible in one place, enforces type safety, and makes it clear which parameters belong to which link.&lt;/p&gt;&lt;p&gt;For links with more complex payloads we can go beyond simple strings or enums. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct RecipeUniversalLinkPayload: Equatable {
    let id: String
    let referrer: String?
}

enum RecipeUniversalLinkPayload {
    case recipe(RecipeUniversalLinkPayload)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we need to support many related links (for example, several different links inside a recipes domain), we can also group them:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UniversalLink {
    case recipes(RecipesUniversalLink)
}

enum RecipesUniversalLink {
    case list
    case detail(id: String)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Centralize parsing&lt;/h3&gt;&lt;p&gt;To centralize all URL parsing logic in one place, we can use a failable or throwing initializer:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension UniversalLink {
    init?(from url: URL) {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return nil }
        guard let path = components.path.split(separator: &amp;quot;/&amp;quot;).map(String.init).last else { return nil }

        switch path {
        case &amp;quot;recipes&amp;quot;:
            self = .recipes
        case &amp;quot;recipe&amp;quot;:
            if let id = components.queryItems?.first(where: { $0.name == &amp;quot;id&amp;quot; })?.value {
                self = .recipe(id: id)
            } else {
                return nil
            }
        case &amp;quot;profile&amp;quot;:
            ...
        default:
            return nil
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of just returning nil, we could alternatively throw a concrete error to be able to show or log a specific error description.&lt;/p&gt;&lt;h3&gt;Integrating with routing&lt;/h3&gt;&lt;p&gt;Once we have a parsed &lt;code&gt;UniversalLink&lt;/code&gt;, we need to integrate it with our app&amp;#x27;s navigation system. If you&amp;#x27;re using a router-based architecture (as described in this article on &lt;a href=&quot;/swiftui-navigation-path-with-tabview/&quot;&gt;using NavigationPath with TabView&lt;/a&gt;), the integration becomes straightforward:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.onOpenURL { url in
    if let link = UniversalLink(from: url) {
        switch link {
        case .recipes:
            appRouter.navigateTo(tab: .recipes)

        case .recipe(let id):
            appRouter.navigateTo(tab: .recipes)
            appRouter.recipesRouter.navigateTo(route: .detail(id: id))

        case .profile(let destination):
            appRouter.navigateTo(tab: .profile)
            appRouter.profileRouter.navigateTo(destination)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Testing universal links&lt;/h3&gt;&lt;p&gt;With this approach, we can ensure quality with unit testing which becomes straightforward. We can test each link type independently:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testRecipeUniversalLink() {
    let url = URL(string: &amp;quot;https://www.example.com/mobile-app/recipe?id=123&amp;quot;)!
    #expect(UniversalLink(from: url) == .recipe(id: &amp;quot;123&amp;quot;))
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can write similar tests for each link type to guarantee that URLs are interpreted correctly.&lt;/p&gt;</content:encoded><category>universal links</category><category>architecture</category><category>navigation</category><category>swiftui</category></item><item><title><![CDATA[Cancelling async tasks in SwiftUI]]></title><description><![CDATA[Learn how to cancel async work in SwiftUI with automatic and manual approaches.]]></description><link>https://tanaschita.com/swiftui-cancel-async-work</link><guid isPermaLink="false">https://tanaschita.com/swiftui-cancel-async-work</guid><pubDate>Sun, 21 Sep 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes we want to cancel async work when certain events happen. A typical example is when a user navigates away from a screen while a network request is still loading. If the result is no longer relevant, we might want the request to stop.&lt;/p&gt;&lt;p&gt;SwiftUI helps us here by tying tasks to the lifecycle of views, which means cancellation often happens automatically. In other cases, we may still want explicit control or even prevent SwiftUI from cancelling the task.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at both.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;A common way to start async work in SwiftUI is with the task modifier. It runs as soon as the view appears and is automatically cancelled as soon as the view disappears:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var items: [Item] = []

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
        .task {
            items = await loadItems()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, if the user leaves the screen before the data arrives, SwiftUI cancels the work and any awaited functions respect that cancellation.&lt;/p&gt;&lt;p&gt;Cancellation does not &amp;quot;unsend&amp;quot; a network request that has already left the device, the server will still process it. But it prevents any further code inside the task from continuing once the cancellation flag is set. For example, if &lt;code&gt;loadItems()&lt;/code&gt; checks for cancellation or performs multiple awaits, it stops earlier.&lt;/p&gt;&lt;p&gt;Sometimes, we want to repeat the work whenever a certain input changes. SwiftUI offers a variant of the same modifier for that: &lt;code&gt;task(id:)&lt;/code&gt;. It runs once when the view appears, and again every time the &lt;code&gt;id&lt;/code&gt; value changes. The old task is cancelled automatically before a new one starts.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SearchView: View {
    @State private var results: [Result] = []
    @State private var query = &amp;quot;&amp;quot;

    var body: some View {
        VStack {
            TextField(&amp;quot;Search&amp;quot;, text: $query)
            List(results) { result in
                Text(result.title)
            }
        }
        .task(id: query) {
            results = try await search(for: query)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This allows us, for example, to handle search or filtering scenarios. The task is cancelled when the user types the next letter, and a new one starts with the updated query. An important note is that each keystroke still triggers a network request. In a real app we would normally add a debounce or delay to avoid flooding the server.&lt;/p&gt;&lt;p&gt;Because Swift concurrency is structured, cancellation automatically propagates down into all awaited calls. If we are running long-running loops or multiple awaits, it can be useful to explicitly check for cancellation using try &lt;code&gt;Task.checkCancellation()&lt;/code&gt; or to handle &lt;code&gt;CancellationError&lt;/code&gt; in our error handling. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func loadItems() async throws -&amp;gt; [Item] {
    var all: [Item] = []
    for page in 1...10 {
        try Task.checkCancellation()
        let batch = try await fetchPage(page)
        all.append(contentsOf: batch)
    }
    return all
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Sometimes we want a task to continue even after the view disappears. For example, when loading data into a shared cache or database that other parts of the app will use.&lt;/p&gt;&lt;p&gt;In that case, we can start a task in a long-lived type, like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class DataService {
    func load() {
        Task {
            let data = await api.load()
            await cache.save(data)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because this task isn&amp;#x27;t tied to a view, SwiftUI will not cancel it when a screen disappears.&lt;/p&gt;&lt;p&gt;If, however, we also want to manually cancel this work (for example, if the user logs out or explicitly cancels a download), we can keep a reference to the task so we can call &lt;code&gt;cancel()&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class DataService {
    private var task: Task&amp;lt;Void, Never&amp;gt;?

    func startWork() {
        task = Task {
            await doSomeWork()
        }
    }

    func cancelWork() {
        task?.cancel()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This way we can integrate manual cancellation into workflows that go beyond the lifetime of a single view. For even finer control, Swift provides &lt;code&gt;withTaskCancellationHandler&lt;/code&gt; which lets us run cleanup code when cancellation happens.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to manage app lifecycle events in SwiftUI]]></title><description><![CDATA[Discover SwiftUI’s scenePhase environment and how to respond to app state changes.]]></description><link>https://tanaschita.com/swiftui-app-lifecycle-events</link><guid isPermaLink="false">https://tanaschita.com/swiftui-app-lifecycle-events</guid><pubDate>Sun, 14 Sep 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI views don&amp;#x27;t just react to their own lifecycle, they can also respond to changes in the state of the entire app. An app might move from active to background, or become inactive when interrupted by a phone call. SwiftUI exposes these transitions through the &lt;code&gt;scenePhase&lt;/code&gt; environment value.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The &lt;code&gt;scenePhase&lt;/code&gt; environment tells us whether the app is currently &lt;code&gt;.active&lt;/code&gt;, &lt;code&gt;.inactive&lt;/code&gt;, or &lt;code&gt;.background&lt;/code&gt;. By observing it with &lt;code&gt;.onChange&lt;/code&gt;, we can easily run code in response to these changes.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(\.scenePhase) private var scenePhase

var body: some View {
    SomeView()
        .onChange(of: scenePhase) { newPhase in
            if newPhase == .active {
                someService.refresh()
            } else if newPhase == .background {
                someService.save()
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes it straightforward to refresh data when the app comes back to the foreground, or to persist state when the app is backgrounded.&lt;/p&gt;&lt;p&gt;Each state has its own meaning:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.active&lt;/code&gt;: the app is running in the foreground and fully interactive.&lt;/li&gt;&lt;li&gt;&lt;code&gt;.inactive&lt;/code&gt;: the app is still in the foreground but not receiving input. This can happen briefly during interruptions like an incoming phone call or when the user pulls down Control Center.&lt;/li&gt;&lt;li&gt;&lt;code&gt;.background&lt;/code&gt;: the app is no longer visible.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In legacy code, you may encounter it being used with UIKit notifications to observe lifecycle changes:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
    viewModel.update()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach and &lt;code&gt;scenePhase&lt;/code&gt; both let us respond when the app comes back to the foreground. The main difference is that &lt;code&gt;onReceive&lt;/code&gt; hooks into UIKit&amp;#x27;s notification system, while &lt;code&gt;scenePhase&lt;/code&gt; is SwiftUI’s built-in, declarative alternative.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding SwiftUI's liquid glass button styles]]></title><description><![CDATA[Learn about different ways to apply Apple's glass style.]]></description><link>https://tanaschita.com/swiftui-glass-button-style</link><guid isPermaLink="false">https://tanaschita.com/swiftui-glass-button-style</guid><pubDate>Tue, 02 Sep 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With SwiftUI&amp;#x27;s liquid glass styles, views and buttons take on a translucent, layered appearance starting in iOS 18. In this article, we&amp;#x27;ll look at the different ways to apply these effects, what each approach gives us, and where their limitations are.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s start with a regular button:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Add&amp;quot;, action: addItem)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, SwiftUI renders this with the system’s standard button style: a text label with minimal decoration that adapts to context. We can of course apply our own &lt;code&gt;ButtonStyle&lt;/code&gt; to fully customize it.&lt;/p&gt;&lt;p&gt;When we instead apply the &lt;code&gt;.glass&lt;/code&gt; style, the button is wrapped in a translucent, frosted layer:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Add&amp;quot;, action: addItem)
    .buttonStyle(.glass)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The style adds depth and makes the button feel like it is floating above the background. It also comes with system behaviors such as a bounce animation when pressed. With this style, we can use modifiers like &lt;code&gt;.tint&lt;/code&gt; to apply an accent color, but other modifiers such as &lt;code&gt;.padding&lt;/code&gt; do not change its appearance because the system fully owns the layout.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:794px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d8a3eddedb6da95dce69260c3d90fc39/99a0d/example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:18.133333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAbUlEQVQY041P0QqAQAjr/z8xCHrqrQgiLPVc7OAqroIGokyds8ENEXFmRkoJZgb1gDpyTa707zsFDSpcAwF3h6rClwE+91DdM8fem9hDsL5MRyIbbOpgYwsRyVz9yS+HzHRDkVU0B2tyX+9S8ACL3DywFsSnWAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;.glass button style&quot; title=&quot;.glass button style&quot; src=&quot;/static/d8a3eddedb6da95dce69260c3d90fc39/99a0d/example.png&quot; srcSet=&quot;/static/d8a3eddedb6da95dce69260c3d90fc39/12d18/example.png 375w,/static/d8a3eddedb6da95dce69260c3d90fc39/44507/example.png 750w,/static/d8a3eddedb6da95dce69260c3d90fc39/99a0d/example.png 794w&quot; sizes=&quot;(max-width: 794px) 100vw, 794px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;A button&amp;#x27;s appearance when using the .buttonStyle(.glass) modifier.&lt;/div&gt;&lt;p&gt;Additionally to &lt;code&gt;.glass&lt;/code&gt;, there is also a more prominent variant &lt;code&gt;.glassProminent&lt;/code&gt; designed to stand out more strongly for primary actions. Note: At the time of writing, this variant is not available in Xcode 26.0 beta and will not compile yet.&lt;/p&gt;&lt;p&gt;Alongside these button styles, SwiftUI also provides the &lt;code&gt;.glassEffect()&lt;/code&gt; modifier. Unlike &lt;code&gt;.glass&lt;/code&gt;, this is a more basic and flexible effect that can be applied to any view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;Hello&amp;quot;)
    .padding()
    .glassEffect()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This simply gives the view a translucent background. It does not add padding, pressed animations, or toolbar integration. That flexibility makes it useful for creating branded buttons or custom views with a glass look, but the extra styling is up to us.&lt;/p&gt;&lt;p&gt;Good to know is also, when we place buttons inside a SwiftUI &lt;code&gt;Toolbar&lt;/code&gt;, SwiftUI may automatically render them with glass styles depending on placement and context:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItem(placement: .confirmationAction) {
        Button(&amp;quot;Save&amp;quot;, action: save)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In many cases, these buttons are styled as glass by default. This means we sometimes get glass-styled buttons without explicitly asking for them. At the moment there is no supported way to opt out of this behavior, so when we place buttons in toolbar slots such as confirmation or cancellation actions, they will always adopt the system&amp;#x27;s glass style.&lt;/p&gt;&lt;p&gt;It is worth noting that glass buttons are not a replacement for custom &lt;code&gt;ButtonStyle&lt;/code&gt;s. If we need branded, app-specific buttons, or dynamic state-driven appearances, custom styles are still the right tool. But when we want to lean on the system look and embrace Apple’s evolving aesthetic, the glass styles are a quick way to get there.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Switching Swift Package Manager dependencies between versioned and local development]]></title><description><![CDATA[Quick tip on switching a self-developed Swift package from a versioned dependency to a local path for development.]]></description><link>https://tanaschita.com/spm-quick-tip-on-switching-to-local-dev</link><guid isPermaLink="false">https://tanaschita.com/spm-quick-tip-on-switching-to-local-dev</guid><pubDate>Sun, 31 Aug 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we extract parts of an app into a Swift package to share between projects, we usually consume that package in our iOS projects through Swift Package Manager with a version tag. This setup works well for sharing and reusing code, since each project depends on a clear versioned release. But when we need to make changes in the package itself, creating a new tag for every experiment quickly becomes impractical. For those cases, we can temporarily switch the package dependency in Xcode from a versioned source to a local path.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;In Xcode, we normally add the package via a Git URL and a version rule, for example a minimum version like &lt;code&gt;from: 1.2.0&lt;/code&gt;. While developing, we can open the project’s &lt;em&gt;Package Dependencies&lt;/em&gt; settings, remove the versioned reference, and add the same package again using the &lt;em&gt;Add Local...&lt;/em&gt; option. This points the dependency directly to the local folder where the package is checked out.&lt;/p&gt;&lt;p&gt;With this setup, any changes we make in the local package sources are immediately reflected in the app project. This allows us to develop and test the updates without pushing commits or tagging new versions. Once the changes are finalized, we can tag a new release of the package and switch the dependency back to the versioned Git URL in Xcode. This way, the project continues to depend on stable versions while still allowing quick iteration during development.&lt;/p&gt;</content:encoded><category>swift package manager</category><category>dependency management</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to use async/await in synchronous Swift code with tasks]]></title><description><![CDATA[Learn how to use tasks to call async/await methods from synchronous code.]]></description><link>https://tanaschita.com/swift-await-async-in-synchronious-swift-code-with-tasks</link><guid isPermaLink="false">https://tanaschita.com/swift-await-async-in-synchronious-swift-code-with-tasks</guid><pubDate>Sun, 24 Aug 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with &lt;code&gt;async/await&lt;/code&gt; in Swift, we might need to call &lt;code&gt;async/await&lt;/code&gt; methods or properties from synchronous contexts. Since those contexts don&amp;#x27;t allow the &lt;code&gt;await&lt;/code&gt; keyword directly, we need a way to bridge the gap. That&amp;#x27;s where tasks come in.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in and look at an example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s say we have an &lt;code&gt;async&lt;/code&gt; function that fetches some data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetchData() async -&amp;gt; String {
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we want to call it when the user taps on a button inside a SwiftUI view.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    var body: some View {
        Button(&amp;quot;Load Data&amp;quot;) {
            await fetchData()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this in place, we get the compiler error &lt;code&gt;async call in a function that does not support concurrency&lt;/code&gt;. A button&amp;#x27;s action is synchronous, so &lt;code&gt;await&lt;/code&gt; doesn&amp;#x27;t work directly. To solve it, we can wrap the call inside a &lt;code&gt;Task&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    var body: some View {
        Button(&amp;quot;Load Data&amp;quot;) {
            Task {
                await fetchData()
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A &lt;code&gt;Task&lt;/code&gt; starts running immediately after creation, and it lets us use await inside otherwise synchronous code. We don&amp;#x27;t need to hold on to it unless we want extra control (like cancellation).&lt;/p&gt;&lt;p&gt;This pattern is useful in many places where async code meets synchronous APIs. For example, in app lifecycle methods such as &lt;code&gt;application(\_:didFinishLaunchingWithOptions:)&lt;/code&gt; in &lt;code&gt;AppDelegate&lt;/code&gt;, we can use the same &lt;code&gt;Task { ... }&lt;/code&gt; approach to call async methods like requesting push notification permissions.&lt;/p&gt;&lt;p&gt;Tasks give us a clean and lightweight way to bridge between Swift&amp;#x27;s structured concurrency and existing synchronous code.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category><category>ios</category></item><item><title><![CDATA[My new book on architecture & design patterns for iOS]]></title><description><![CDATA[I finally finished my new book on building scalable and maintainable apps with Swift and SwiftUI.]]></description><link>https://tanaschita.com/book-ios-architecture-design-patterns</link><guid isPermaLink="false">https://tanaschita.com/book-ios-architecture-design-patterns</guid><pubDate>Mon, 18 Aug 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I finally finished my second book 🥳&lt;/p&gt;&lt;p&gt;It&amp;#x27;s about iOS architecture and design patterns - a topic that has challenged me and shaped the way I think when writing code. Excited (and a bit nervous) to share it.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.&lt;/p&gt;&lt;p&gt;To learn more about it, check out the &lt;a href=&quot;/books/&quot;&gt;books section&lt;/a&gt; of this site.&lt;/p&gt;</content:encoded><category>book</category><category>architecture</category><category>design patterns</category><category>swift</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[How to create a custom reusable toolbar in SwiftUI]]></title><description><![CDATA[Learn how to abstract and reuse a SwiftUI toolbar across multiple screens.]]></description><link>https://tanaschita.com/swiftui-reusable-toolbar</link><guid isPermaLink="false">https://tanaschita.com/swiftui-reusable-toolbar</guid><pubDate>Mon, 11 Aug 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building navigation bars and toolbars in SwiftUI, we&amp;#x27;ll often want to keep the same look and functionality across multiple screens. Instead of copying and pasting the same toolbar code everywhere, we can abstract the toolbar into a reusable component.&lt;/p&gt;&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;ToolbarContent&lt;/code&gt; protocol is perfect to do that. In this guide, we&amp;#x27;ll create a custom reusable toolbar, apply it via a &lt;code&gt;ViewModifier&lt;/code&gt;, and add a convenient view extension for a one-line API.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The following example creates a navigation bar with a custom back button and a title:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeCustomToolBarContent: ToolbarContent {

    let title: String

    @Environment(\.presentationMode) var presentationMode

    var body: some ToolbarContent {
        ToolbarItemGroup(placement: .navigationBarLeading) {
            Button(action: {
                presentationMode.wrappedValue.dismiss()
            }) {
                Image(&amp;quot;someCustomBackButtonImage&amp;quot;)
            }
        }
        ToolbarItem(placement: .principal) {
            Text(title)
                .font(titleFont)
                .foregroundColor(titleColor)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To use it, we can use SwiftUI&amp;#x27;s toolbar modifier as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;someView
    .toolbar {
        SomeCustomToolBarContent(title: &amp;quot;SomeTitle&amp;quot;)
    }
    .navigationBarBackButtonHidden(true)
    .navigationBarShadow()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can go a step further to add even more abstraction by creating a custom view modifier:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeCustomToolBarModifier: ViewModifier {

    let title: String

    func body(content: Content) -&amp;gt; some View {
        return content
            .toolbar {
                SomeCustomToolBarContent(title: title)
            }
            .navigationBarBackButtonHidden(true)
            .navigationBarShadow()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that in place, we can now apply the toolbar as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;someView
    .modifier(SomeCustomToolBarModifier(title: &amp;quot;SomeTitle&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To make the call above even shorter, we could additionally create a &lt;code&gt;View&lt;/code&gt; extension:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension View {
    func someCustomToolbar(title: String) -&amp;gt; some View {
        return self.modifier(SomeCustomToolBarModifier(title: title))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we are able to apply the toolbar with the following call:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;someView
    .someCustomToolbar(title: &amp;quot;SomeTitle&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to use the @available attribute in Swift]]></title><description><![CDATA[Learn to make Swift code only available for certain language versions or platforms.]]></description><link>https://tanaschita.com/swift-available-attribute</link><guid isPermaLink="false">https://tanaschita.com/swift-available-attribute</guid><pubDate>Sun, 03 Aug 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In Swift, we have the possibility to add attributes to our code to give the compiler more information. One of those attributes is &lt;code&gt;@available&lt;/code&gt; which we can use to make our code only available for certain Swift language versions or platforms.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s take a look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Attributes in Swift&lt;/h3&gt;&lt;p&gt;In general, an attribute in Swift is always specified with the &lt;code&gt;@&lt;/code&gt; symbol followed by the attribute&amp;#x27;s name and optionally its arguments enclosed in parentheses. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@objc
class SomeClass: NSObject {}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;or with arguments:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@available(iOS 15.0, *)
func doSomething() {}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The @available attribute&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;@available&lt;/code&gt; attribute lets us define the availability of functions, types, properties, and other declarations across specific platforms or Swift versions. This is especially useful when deprecating APIs, renaming methods, or introducing platform- or version-specific features.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;@available&lt;/code&gt; attribute always has two or more arguments, separated by a comma. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@available(*, unavailable, renamed: &amp;quot;updateCategories&amp;quot;)
func update() {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the above example, we declare the &lt;code&gt;update&lt;/code&gt; method as not available on any platform because it was renamed to &lt;code&gt;updateCategories&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Calling the &lt;code&gt;update&lt;/code&gt; method will now fail with a compiler error:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;update() has been renamed to updateCategories()
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Arguments of @available attribute&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s look at the arguments of the &lt;code&gt;@available&lt;/code&gt; attribute in more detail.&lt;/p&gt;&lt;p&gt;The first argument is always a platform or language name, for example &lt;code&gt;iOS&lt;/code&gt;, &lt;code&gt;iOSApplicationExtension&lt;/code&gt;, &lt;code&gt;swift&lt;/code&gt;. Alternatively, we can use an asterisk &lt;code&gt;*&lt;/code&gt; to indicate that the availability applies to all platforms.&lt;/p&gt;&lt;p&gt;The remaining arguments can appear in any order:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;unavailable&lt;/code&gt; - to indicate that a declaration isn&amp;#x27;t available on the specified platform&lt;/li&gt;&lt;li&gt;&lt;code&gt;introduced: &amp;lt;version number&amp;gt;&lt;/code&gt; - to specify the first version in which a declaration was introduced&lt;/li&gt;&lt;li&gt;&lt;code&gt;deprecated: &amp;lt;version number&amp;gt;&lt;/code&gt; - to specify the first version when a declaration was deprecated&lt;/li&gt;&lt;li&gt;&lt;code&gt;obsoleted: &amp;lt;version number&amp;gt;&lt;/code&gt; - to specify the first version when a declaration was obsoleted and can no longer be used&lt;/li&gt;&lt;li&gt;&lt;code&gt;message: &amp;lt;message&amp;gt;&lt;/code&gt; - to provide a message for the compiler to display when emitting a warning or error&lt;/li&gt;&lt;li&gt;&lt;code&gt;renamed: &amp;lt;new name&amp;gt;&lt;/code&gt; - to provide a new name&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;version number&amp;gt;&lt;/code&gt; consists of one to three positive integers separated by periods, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@available(iOS, introduced: 17.0)
struct Content {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes &lt;code&gt;Content&lt;/code&gt; available starting from iOS 17.0. It won&amp;#x27;t be available in earlier versions. If we want to use certain code paths on older versions as well, we can use an &lt;code&gt;if #available&lt;/code&gt; check to conditionally execute code based on platform version at runtime:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if #available(iOS 17.0, *) {
    // Content is available here
} else {
    // Fallback to some other code
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Combination of @available attributes&lt;/h3&gt;&lt;p&gt;We can combine multiple availability declarations as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@available(swift, introduced: 5.10)
@available(iOS, introduced: 17.0)
struct Content {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This makes &lt;code&gt;Content&lt;/code&gt; available only if both Swift version is at least 5.10 and the app is running on iOS 17.0 or later.&lt;/p&gt;&lt;h3&gt;Shorthand syntax&lt;/h3&gt;&lt;p&gt;In case that only an &lt;code&gt;introduced&lt;/code&gt; argument is used, we can simplify using a shorthand syntax:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@available(swift 5.10)
@available(iOS 17.0)
struct Content {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is equivalent to the previous example, just written using the shorthand syntax.&lt;/p&gt;</content:encoded><category>swift</category><category>ios</category></item><item><title><![CDATA[Overview on Accessibility Nutrition Labels for iOS development]]></title><description><![CDATA[Get an overview of what Accessibility Nutrition Labels are and how to support them.]]></description><link>https://tanaschita.com/ios-accessibility-nutrition-labels</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-nutrition-labels</guid><pubDate>Sun, 27 Jul 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC25, Apple introduced Accessibility Nutrition Labels. These labels appear on the App Store and give users a summary of which accessibility features an app supports.&lt;/p&gt;&lt;p&gt;As developers, we can declare if our app supports specific accessibility features such as VoiceOver, Dynamic Type etc. In this article, we&amp;#x27;ll go through exisiting Accessibility Nutrition Labels and how to support them.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;VoiceOver&lt;/h3&gt;&lt;p&gt;VoiceOver is a screen reader, which helps users navigate through the app without seeing the screen. For native UI elements, SwiftUI already provides VoiceOver support out of the box. But in many cases, we still need some manual work to improve the Voiceover experience, for example by providing appropriate &lt;code&gt;accessibilityLabel&lt;/code&gt;s and traits for custom controls, grouping views, and more.&lt;/p&gt;&lt;p&gt;To learn more about supporting Voiceover accessibility, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-voiceover-gestures/&quot;&gt;supporting VoiceOver in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Voice Control&lt;/h3&gt;&lt;p&gt;Voice Control is an iOS accessibility feature that allows users to control their device and interact with apps entirely by voice. It&amp;#x27;s especially helpful for users with reduced mobility or dexterity, enabling them to navigate, tap buttons, scroll, and dictate text using only their voice.&lt;/p&gt;&lt;p&gt;When Voice Control is enabled, users can speak commands to interact with UI elements, and the system overlays numbers or names on tappable elements that users can reference in their commands. Most of the work to make an app accessible to Voice Control overlaps with VoiceOver support, ensuring all interactive elements have clear labels and are reachable through accessibility navigation.&lt;/p&gt;&lt;h3&gt;Larger Text&lt;/h3&gt;&lt;p&gt;On iOS devices, users can increase text size globally in the devices settings to make texts more visible and comfortable to read. We can support this in our app by supporting Dynamic Type and optimize our apps to be readable with large size settings.&lt;/p&gt;&lt;p&gt;To learn more, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-voiceover-gestures/&quot;&gt;how to support Dynamic Type in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Dark Interface&lt;/h3&gt;&lt;p&gt;Dark Interface support helps users with light sensitivity to reduce discomfort when using an app. If our app isn’t dark by default for all common tasks, we can support this feature by adding Dark Mode support.&lt;/p&gt;&lt;p&gt;To learn more, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-supporting-dark-mode-programmatically/&quot;&gt;how to support dark mode in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Differentiate Without Color Alone&lt;/h3&gt;&lt;p&gt;Supporting this feature ensures that the app doesn&amp;#x27;t exclude users who may not be able to perceive all colors. We can communicate states and values through additional visual cues such as text, icons, shapes or patterns.&lt;/p&gt;&lt;p&gt;SwiftUI also lets us respond to the system&amp;#x27;s &lt;em&gt;Differentiate Without Color&lt;/em&gt; setting. To learn more, check out the article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-differentiate-without-color/&quot;&gt;supporting Differentiate Without Color in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Using the Grayscale Color Filter under &lt;code&gt;Accessibility → Display &amp;amp; Text Size → Color Filters&lt;/code&gt; can help reveal places that rely on color alone. It is a useful additional check, but not a simulation of every color vision difference.&lt;/p&gt;&lt;h3&gt;Sufficient Contrast&lt;/h3&gt;&lt;p&gt;The goal of this feature is to support low-vision users. We can use the Color Contrast Calculator tool which can be opened through Xcodes&amp;#x27;s Accessibiltiy Inspector and check if our values pass the contrast test.&lt;/p&gt;&lt;p&gt;To learn more, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-contrast/&quot;&gt;supporting sufficient contrast accessibility with Xcode&amp;#x27;s Color Contrast Calculator&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Reduced Motion&lt;/h3&gt;&lt;p&gt;The goal of supporting the &lt;em&gt;Reduced Motion&lt;/em&gt; setting is to support users who are sensitive to visual motion. Motion sensitivity can cause discomfort such as nausea, dizziness, or headaches, especially when animations include scaling, spinning, or rapid movement.&lt;/p&gt;&lt;p&gt;Users can enable this setting in the iOS Settings app under &lt;code&gt;Accessibility → Motion → Reduce Motion&lt;/code&gt;. SwiftUI provides the &lt;code&gt;accessibilityReduceMotion&lt;/code&gt; environment value, which we can use to check whether &lt;em&gt;Reduced Motion&lt;/em&gt; is enabled.&lt;/p&gt;&lt;p&gt;To learn more, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-reduced-motion/&quot;&gt;supporting Reduced Motion accessibility setting in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Captions&lt;/h3&gt;&lt;p&gt;Captions provide a text-based alternative to spoken content, supporting users who are deaf or hard of hearing. To support this feature, we can ensure that our video content includes caption tracks, for example by using &lt;code&gt;.srt&lt;/code&gt; files or embedding caption metadata directly in the media and reflect users settings.&lt;/p&gt;&lt;p&gt;On iOS, system video players like &lt;code&gt;AVPlayerViewController&lt;/code&gt; automatically support captions when available in the media file.&lt;/p&gt;&lt;p&gt;Users can enable system-wide captions in the iOS Settings under &lt;code&gt;Accessibility&lt;/code&gt; → &lt;code&gt;Subtitles &amp;amp; Captioning&lt;/code&gt;. To check if the user has enabled captions we can use &lt;code&gt;UIAccessibility.isClosedCaptioningEnabled&lt;/code&gt;. To respond to changes at runtime, we can observe &lt;code&gt;UIAccessibility.closedCaptioningStatusDidChangeNotification&lt;/code&gt; via &lt;code&gt;NotificationCenter&lt;/code&gt;. As of iOS 18, there is no SwiftUI environment value for this setting.&lt;/p&gt;&lt;h3&gt;Audio Descriptions&lt;/h3&gt;&lt;p&gt;Audio descriptions provide spoken narration of visual content for users who are blind or have low vision. These descriptions typically explain important visual information, such as actions, settings, or scene changes.&lt;/p&gt;&lt;p&gt;To support this feature, we can include separate audio tracks with descriptive narration, and ensure our media player allows users to select those tracks if available. Apple&amp;#x27;s &lt;code&gt;AVPlayer&lt;/code&gt; supports audio description tracks out of the box, provided they are embedded in the media asset.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Using defer in Swift to manage state cleanup]]></title><description><![CDATA[Learn how to use defer to handle cleanup logic in async functions.]]></description><link>https://tanaschita.com/swift-defer</link><guid isPermaLink="false">https://tanaschita.com/swift-defer</guid><pubDate>Sun, 20 Jul 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with async code, we often need to manage UI state before and after an operation. A common example is showing and hiding a loading indicator.&lt;/p&gt;&lt;p&gt;In Swift, we can use the &lt;code&gt;defer&lt;/code&gt; statement to ensure that cleanup code runs when the current scope exits, regardless of how we exit, successfully or due to an error.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at a typical use case.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetch() async {
    isLoading = true
    defer { isLoading = false }

    do {
        articles = try await service.fetchArticles()
    } catch {
        self.error = error.localizedDescription
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, we want to show a loading indicator while fetching data and hide it afterward. The defer block ensures that &lt;code&gt;isLoading = false&lt;/code&gt; is always called when the function completes, whether it finishes normally or throws an error.&lt;/p&gt;&lt;p&gt;Without defer, we would achieve the same result in different ways. For example by setting &lt;code&gt;isLoading = false&lt;/code&gt; in multiple places:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetch() async {
    isLoading = true

    do {
        articles = try await service.fetchArticles()
        isLoading = false
    } catch {
        self.error = error.localizedDescription
        isLoading = false
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or by placing it once at the bottom of the function:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetch() async {
    isLoading = true

    do {
        articles = try await service.fetchArticles()
    } catch {
        self.error = error.localizedDescription
    }

    isLoading = false
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both approaches work, especially in small functions like this. But as a function grows, it&amp;#x27;s easy to introduce subtle bugs. For example, if we add a new return somewhere, or if the task is cancelled during an await, we could accidentally skip resetting the state.&lt;/p&gt;&lt;p&gt;That&amp;#x27;s where defer helps. It guarantees that the block will run when the surrounding scope exits, in all of the following cases:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The function runs normally to the end&lt;/li&gt;&lt;li&gt;An error is thrown&lt;/li&gt;&lt;li&gt;There&amp;#x27;s an early return&lt;/li&gt;&lt;li&gt;The task is cancelled during an &lt;code&gt;await&lt;/code&gt; call&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By centralizing cleanup logic with defer in one place, we reduce the chance of mistakes and make our code more robust, especially in async or error-prone contexts.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Supporting Reduced Motion accessibility setting in SwiftUI]]></title><description><![CDATA[Learn how to respect system settings for reduced motion to improve usability for motion-sensitive users.]]></description><link>https://tanaschita.com/ios-accessibility-reduced-motion</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-reduced-motion</guid><pubDate>Sun, 13 Jul 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The goal of supporting the &lt;em&gt;Reduced Motion&lt;/em&gt; setting is to support users who are sensitive to visual motion. Motion sensitivity can cause discomfort such as nausea, dizziness, or headaches, especially when animations include scaling, spinning, or rapid movement.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how we can support &lt;em&gt;Reduced Motion&lt;/em&gt; in our iOS apps.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Users can enable this setting in the iOS Settings app under &lt;code&gt;Accessibility → Motion → Reduce Motion&lt;/code&gt;. If enabled, users may also choose to &lt;code&gt;Prefer Cross-Fade Transitions&lt;/code&gt;, which replaces more intense system animations with simple fade transitions, for example, when opening or closing apps.&lt;/p&gt;&lt;p&gt;We can respect this setting by reducing or disabling non-essential animations in our apps, especially those that serve decorative or transitional purposes.&lt;/p&gt;&lt;p&gt;SwiftUI provides the &lt;code&gt;accessibilityReduceMotion&lt;/code&gt; environment value, which we can use to check whether &lt;em&gt;Reduced Motion&lt;/em&gt; is enabled:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(\.accessibilityReduceMotion) private var reduceMotion
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this, we can conditionally modify animations based on the user’s preference:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct AnimatedLogo: View {
    @Environment(\.accessibilityReduceMotion) private var reduceMotion

    var body: some View {
        Image(systemName: &amp;quot;star.fill&amp;quot;)
            .rotationEffect(reduceMotion ? .zero : .degrees(360))
            .animation(
                reduceMotion ? nil : .linear(duration: 1).repeatForever(autoreverses: false),
                value: reduceMotion
            )
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, if &lt;em&gt;Reduce Motion&lt;/em&gt; is enabled, the animation is disabled entirely. Otherwise, the image spins continuously.&lt;/p&gt;&lt;p&gt;We could also consider switching to simpler transition styles, like using &lt;code&gt;.opacity&lt;/code&gt; instead of &lt;code&gt;.scaleEffect&lt;/code&gt; or &lt;code&gt;.offset&lt;/code&gt;, to respect the user&amp;#x27;s preference while maintaining functional transitions.&lt;/p&gt;&lt;p&gt;Note: If we also wanted to check whether &lt;em&gt;Prefer Cross-Fade Transitions&lt;/em&gt; is enabled, we can use &lt;code&gt;UIAccessibility.prefersCrossFadeTransitions&lt;/code&gt;. There&amp;#x27;s currently no environment value available for that setting in SwiftUI.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Supporting sufficient contrast accessibility with Xcode's Color Contrast Calculator]]></title><description><![CDATA[Quick tip on how to check if colors meet accessibility contrast requirements using Xcode’s built-in tools.]]></description><link>https://tanaschita.com/ios-accessibility-contrast</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-contrast</guid><pubDate>Sun, 06 Jul 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Supporting sufficient color contrast helps users with low vision distinguish text, icons, and UI elements more easily. To verify if our color choices meet accessibility guidelines, we can use Xcode’s built-in Color Contrast Calculator.&lt;/p&gt;&lt;p&gt;The Color Contrast Calculator is part of the Accessibility Inspector. It can be opened via the menu bar under &lt;code&gt;Xcode → Open Developer Tool → Accessibility Inspector&lt;/code&gt;, then choosing &lt;code&gt;Window → Show Color Contrast Calculator&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Here, we can pick foreground and background colors using the color pickers in the calculator or use the color picker tool to sample from any part of our running app.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8aa36afd8e9aa5ee50dc76a092e306c6/9b427/ccc.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:35.46666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB1ElEQVQozx2RW4sSARiG5zd1FcRC0GraH+iym4iF7oUIdqHFlmXpouu66IBQ0Lar4aHQtZzSktbDmqfRnHF0Ds7ouKNrsgY94Vx8fC/fxcPD+wnXNzbIJN5R+CFSLHxHFL+SzWaJxT6QSCSIxxOk0xnvLooiuVyO/Lc8h0fHRCKvSEVTHL6JcpI6ZmvrPkIw4KfSbJIu/qTcllgsLnCcCYahe9manjOanmO7M8buzMuXf1f0+wqWMeFzI8lJ4SX8+0M4vIdwK+DndfwjD59HeBZLMhqZLJdLVqsVrusiGyYdzaAzNOhqBtJQ96Bqr0dV7nFt/y6hO1dQCyJ7T54i+H2b1M4qXC4XLC5maNqQTkdiPp97lppl0x1qyLqBYpj81nQcd4Y+GCANB9y+94LtqztYtSbh/QME3+YN2g2JkeUiD0x0Q0dRZBzH8WxN00BRFKZTxxt7bGNZI3qyzC9V47Qu8yVXotZuEQo9QAj6AxR7InW1hdIfeQDbthiPbSaTsQdZg6vVCuVyyetuNnPpdLu0JAlV7lKX6li2yaPdxwhB303y3Qxt7QxtMERV+x5s/Zg1cL3XRs1mA0lqo+uaV03+tMTRpwypZIq30fc0GhW2d3b5D1Dc2Qom6/jcAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s Color Contrast Calculator&quot; title=&quot;Xcode&amp;#x27;s Color Contrast Calculator&quot; src=&quot;/static/8aa36afd8e9aa5ee50dc76a092e306c6/2e9ed/ccc.png&quot; srcSet=&quot;/static/8aa36afd8e9aa5ee50dc76a092e306c6/12d18/ccc.png 375w,/static/8aa36afd8e9aa5ee50dc76a092e306c6/44507/ccc.png 750w,/static/8aa36afd8e9aa5ee50dc76a092e306c6/2e9ed/ccc.png 1500w,/static/8aa36afd8e9aa5ee50dc76a092e306c6/9b427/ccc.png 1894w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s Color Contrast Calculator&lt;/div&gt;&lt;p&gt;The contrast ratio will update after changing a color and show whether the chosen colors pass the contrast accessibility guidelines.&lt;/p&gt;</content:encoded><category>accessibility</category><category>ios</category></item><item><title><![CDATA[How to support Dynamic Type accessibility in SwiftUI]]></title><description><![CDATA[Learn how to support font scaling for user's custom text size settings.]]></description><link>https://tanaschita.com/ios-accessibility-dynamic-type</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-dynamic-type</guid><pubDate>Sun, 29 Jun 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dynamic Type allows users to increase or decrease the size of text in apps based on their reading preferences. This can be configured in the iOS Settings under &lt;code&gt;Accessibility&lt;/code&gt; → &lt;code&gt;Display &amp;amp; Text Size&lt;/code&gt; → &lt;code&gt;Larger Text&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Supporting Dynamic Type ensures that our app remains readable and accessibible to more users. In this article, we&amp;#x27;ll look at how to support Dynamic Type in a SwiftUI app.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using system fonts&lt;/h3&gt;&lt;p&gt;When we&amp;#x27;re using SwiftUI&amp;#x27;s built-in text styles like &lt;code&gt;.body&lt;/code&gt;, &lt;code&gt;.headline&lt;/code&gt;, or &lt;code&gt;.title&lt;/code&gt;, Dynamic Type is automatically supported:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello&amp;quot;)
    .font(.body)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These styles scale according to the user’s preferred content size category and follow the system&amp;#x27;s typographic hierarchy.&lt;/p&gt;&lt;h3&gt;Using custom fonts&lt;/h3&gt;&lt;p&gt;We can also use custom fonts with scaling. Here&amp;#x27;s a simple example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello&amp;quot;)
    .font(.custom(&amp;quot;SomeCustomFont&amp;quot;, size: 24))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As of iOS 14 and later, this version will also scale automatically with the system&amp;#x27;s text size setting.&lt;/p&gt;&lt;p&gt;When using custom fonts, we can specify a relative text style:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello&amp;quot;)
    .font(.custom(&amp;quot;SomeCustomFont&amp;quot;, size: 24, relativeTo: .title))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Without the &lt;code&gt;relativeTo&lt;/code&gt; parameter, it defaults to &lt;code&gt;.body&lt;/code&gt;. Setting it gives us more control over how strongly the font reacts to the user&amp;#x27;s text size setting, for example a &lt;code&gt;.title&lt;/code&gt; will grow more aggressively than &lt;code&gt;.body&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Adapting layout with @ScaledMetric&lt;/h3&gt;&lt;p&gt;Supporting Dynamic Type isn&amp;#x27;t just about text - it may affect other layout settings like spacing, padding etc. To adapt layout values like padding or spacing, we can use the &lt;code&gt;@ScaledMetric&lt;/code&gt; property wrapper:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@ScaledMetric(relativeTo: .body) var padding: CGFloat = 10

Text(&amp;quot;Hello&amp;quot;)
    .padding(scaledPadding)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This value will scale just like .body would, which helps maintain proportional spacing and a visually balanced layout as text grows.&lt;/p&gt;&lt;h3&gt;Opting out of scaling&lt;/h3&gt;&lt;p&gt;In rare cases, we might want a text element to stay at a fixed size regardless of the user&amp;#x27;s text size setting. For example, in tight UI elements where scaling might cause layout issues. In these cases, we can use &lt;code&gt;fixedSize&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;Hello&amp;quot;)
    .font(.custom(&amp;quot;SomeCustomFont&amp;quot;, fixedSize: 20))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;However, fixed-size text may become unreadable for some users and should only be used where truly necessary.&lt;/p&gt;&lt;p&gt;Scaling the font is only one part of supporting larger text. When growing content no longer fits its original structure, we can also &lt;a href=&quot;/ios-accessibility-dynamic-type-layouts/&quot;&gt;adapt the SwiftUI layout for accessibility text sizes&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Using WebKit with SwiftUI to manage web content]]></title><description><![CDATA[Get an overview on how to use WebView and WebPage in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-webkit</link><guid isPermaLink="false">https://tanaschita.com/swiftui-webkit</guid><pubDate>Sun, 22 Jun 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting with iOS 17, WebKit provides new tools to integrate web content directly within SwiftUI. Two key types introduced are &lt;code&gt;WebView&lt;/code&gt; and &lt;code&gt;WebPage&lt;/code&gt;, both of which enable us to display and interact with web content within SwiftUI views.&lt;/p&gt;&lt;p&gt;In this article, we’ll cover the basics of these types, highlighting when to use each. Additionally, we&amp;#x27;ll look at how to support iOS versions prior to iOS 17.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using WebView&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;WebView&lt;/code&gt; type combined with the &lt;code&gt;URL&lt;/code&gt; parameter is the most direct way to display web content in a SwiftUI app. The &lt;code&gt;WebView&lt;/code&gt; handles the job of loading and displaying a web page.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {

    var body: some View {
        WebView(url: url)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;#x27;s great for static content or when we don&amp;#x27;t need to interact with the page.&lt;/p&gt;&lt;h3&gt;Working with WebPage&lt;/h3&gt;&lt;p&gt;For more control over web content, we can use &lt;code&gt;WebPage&lt;/code&gt; to load and control the web content and interact with it programmatically. &lt;code&gt;WebPage&lt;/code&gt; is particularly useful when we need to react to events like navigation changes, run JavaScript on the page, or track the state of the page.&lt;/p&gt;&lt;p&gt;Here is an example setup:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var page = WebPage()

    var body: some View {
        WebView(page)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now interact with the page and the web view will update accordingly if changes occur.&lt;/p&gt;&lt;p&gt;For example, we can load web content with a &lt;code&gt;URLRequest&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;page.load(urlRequest)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or we can load HTML content directly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;page.load(html: htmlString, baseURL: baseUrl)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or to load data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;page.load(webArchiveData, mimeType: &amp;quot;application/x-webarchive&amp;quot;, characterEncoding: .utf8, baseURL: baseURL)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally, we can use &lt;code&gt;WebPage&lt;/code&gt;&amp;#x27;s &lt;code&gt;currentNavigationEvent&lt;/code&gt; property to track the navigation state of a page such as when a page starts loading, redirects, when it&amp;#x27;s done, or if it encounters an error.&lt;/p&gt;&lt;p&gt;It also allows us to run JavaScript functions within the web page by using the &lt;code&gt;callJavaScript&lt;/code&gt; API. This can be useful for tasks like extracting information from the page or modifying the content dynamically.&lt;/p&gt;&lt;h3&gt;Supporting versions prior to iOS 17&lt;/h3&gt;&lt;p&gt;Since WebKit&amp;#x27;s native SwiftUI integration with &lt;code&gt;WebView&lt;/code&gt; and &lt;code&gt;WebPage&lt;/code&gt; is available only starting with iOS 17, we can use &lt;code&gt;WKWebView&lt;/code&gt; and bridge it with SwiftUI using &lt;code&gt;UIViewRepresentable&lt;/code&gt; for earlier versions.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    var url: URL

    func makeUIView(context: Context) -&amp;gt; WKWebView {
        let webView = WKWebView()
        return webView
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        let request = URLRequest(url: url)
        uiView.load(request)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, we create a &lt;code&gt;WebView&lt;/code&gt; struct that conforms to &lt;code&gt;UIViewRepresentable&lt;/code&gt;. This allows us to integrate &lt;code&gt;WKWebView&lt;/code&gt; into a SwiftUI view hierarchy.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    var body: some View {
        WebView(url: url)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When supporting both, you can use conditional checks to apply the correct implementation based on the iOS version.&lt;/p&gt;</content:encoded><category>webkit</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[WWDC 2025 summary of Apple's platforms state of the union talk for iOS developers]]></title><description><![CDATA[Discover Apple's announcements for iOS at WWDC 2025.]]></description><link>https://tanaschita.com/wwdc-2025-state-of-the-union-summary</link><guid isPermaLink="false">https://tanaschita.com/wwdc-2025-state-of-the-union-summary</guid><pubDate>Wed, 11 Jun 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article summarizes the key announcements for iOS developers from Apple&amp;#x27;s Platforms State of the Union talk at WWDC 2025.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Liquid Glass Design&lt;/h3&gt;&lt;p&gt;Apple introduced &lt;em&gt;Liquid Glass&lt;/em&gt;, a new design element with the goal to enhance the depth and fluidity of UI components in iOS, iPadOS, and macOS. It is applied to many native UI components such as buttons, navigation bars, and toolbars.&lt;/p&gt;&lt;p&gt;When we recompile our apps with Xcode 26, most system components will automatically adopt the &lt;em&gt;Liquid Glass&lt;/em&gt; design. UI elements such as &lt;code&gt;TabView&lt;/code&gt;, &lt;code&gt;NavigationSplitView&lt;/code&gt;, and toolbars will update without requiring any changes on our part.&lt;/p&gt;&lt;p&gt;A new API for handling toolbars, navigation bars, and custom controls allows for more precise control over how these elements appear and behave. For example, custom controls can now be embedded in a &lt;code&gt;glassEffect&lt;/code&gt; modifier to apply the new dynamic effects that Liquid Glass introduces.&lt;/p&gt;&lt;p&gt;Apple also provides a way to opt-out or disable the effect on a per-view basis until we fully adapt to the new material.&lt;/p&gt;&lt;h3&gt;App Icons &amp;amp; Icon Composer&lt;/h3&gt;&lt;p&gt;Along with Liquid Glass, Apple introduced new icon designs, with support for two new modes: clear mode and tint mode. Clear mode allows the icon to fully showcase the Liquid Glass material, while tint mode offers a more colorful look, with different light and dark variants.&lt;/p&gt;&lt;p&gt;To streamline the creation of these new icons, Apple introduced Icon Composer. This tool helps us create multi-layered app icons, optimize transparency, and preview the final icon in different states before using it in our apps.&lt;/p&gt;&lt;h3&gt;Apple Intelligence API&lt;/h3&gt;&lt;p&gt;Apple expanded its Apple Intelligence capabilities with the introduction of the &lt;strong&gt;Foundation Models framework&lt;/strong&gt;. This framework gives us access to on-device machine learning models for tasks such as text extraction, summarization, and dynamic content generation.&lt;/p&gt;&lt;p&gt;One key feature within this framework is Guided Generation, which enables us to generate structured data types based on user input. This is particularly useful for creating apps with dynamic, data-driven content that adapts to user preferences or inputs.&lt;/p&gt;&lt;p&gt;By using these on-device models, we can offer AI-powered features in our apps without requiring an internet connection, ensuring both speed and privacy for the end user.&lt;/p&gt;&lt;h3&gt;Xcode 26&lt;/h3&gt;&lt;p&gt;Xcode 26 introduces several useful features. One of them the integration of &lt;strong&gt;ChatGPT directly within the IDE&lt;/strong&gt;, allowing for code completion and natural language interaction directly within Xcode.&lt;/p&gt;&lt;h3&gt;Swift 6.2&lt;/h3&gt;&lt;p&gt;Swift 6.2 introduces updates aimed at improving performance and memory management.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inline arrays&lt;/strong&gt;: Swift now supports inline arrays, allowing us to store small arrays on the stack instead of the heap. This reduces memory overhead and improves performance, particularly in performance-critical parts of our apps.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Span type&lt;/strong&gt;: A new memory-safe span type is introduced, making it easier to work with contiguous blocks of memory. This provides an alternative to using raw pointers, improving safety when interacting with low-level data or C libraries.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrency improvements&lt;/strong&gt;: Swift now includes a new &lt;code&gt;concurrent&lt;/code&gt; attribute that allows us to offload tasks to background threads more easily&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;SwiftUI updates&lt;/h3&gt;&lt;p&gt;SwiftUI continues to evolve with several updates aimed at improving app performance and expanding capabilities.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web content support&lt;/strong&gt;: With the new &lt;code&gt;WebView&lt;/code&gt; component, we can now integrate web content directly into SwiftUI apps. Using &lt;code&gt;WebPage&lt;/code&gt;, we can observe and control web content from within our app.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rich text editing&lt;/strong&gt;: We can now use &lt;code&gt;AttributedString&lt;/code&gt; when working with &lt;code&gt;TextEditor&lt;/code&gt;. This is useful for apps that require text formatting while editing, such as note-taking or messaging apps.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance improvements&lt;/strong&gt;: SwiftUI has introduced idle prefetching to improve the performance of lists and tables. By preloading data during idle times, this feature reduces latency and makes scrolling through large datasets feel smoother and more responsive.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;VisionOS updates&lt;/h3&gt;&lt;p&gt;Apple continues to develop visionOS, the operating system for spatial computing. There are new updates for creating 3D and AR experiences. One of the main updates is support for shared spatial experiences, where multiple users can interact with the same virtual objects in a shared physical space.&lt;/p&gt;</content:encoded><category>wwdc</category><category>ios</category></item><item><title><![CDATA[Adding navigation buttons to the keyboard in SwiftUI]]></title><description><![CDATA[Learn how to add navigation buttons above the keyboard to switch between text fields in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-keyboard-buttons</link><guid isPermaLink="false">https://tanaschita.com/swiftui-keyboard-buttons</guid><pubDate>Sat, 07 Jun 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building forms with multiple textfields in SwiftUI, it can improve user experience by adding navigation buttons above the keyboard to allow users to navigate between these textfields more easily. Let&amp;#x27;s look at how we can implement this.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Setting up the basic text fields&lt;/h3&gt;&lt;p&gt;To be able to control which textfield is currently focused, we can use the &lt;code&gt;@FocusState&lt;/code&gt; property:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct FormView: View {
    @FocusState private var focusedField: Field?

    enum Field: Hashable {
        case name, address, mail, phone ...
    }

    var body: some View {
        VStack {
            TextField(&amp;quot;textfield.name&amp;quot;.localized, text: $name)
                .focused($focusedField, equals: .name)

            // more textfields

            Spacer()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;@FocusState&lt;/code&gt; property is used to bind the focus of each text field to a corresponding case in the &lt;code&gt;Field&lt;/code&gt; enum. This allows us to shift the focus programmatically.&lt;/p&gt;&lt;h3&gt;Adding navigation buttons to the toolbar&lt;/h3&gt;&lt;p&gt;Next, we can add the navigation buttons above the keyboard using the &lt;code&gt;toolbar()&lt;/code&gt; modifier:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    VStack {
        // TextField setup...

        Spacer()
    }
    .toolbar {
        ToolbarItemGroup(placement: .keyboard) {
            Button(action: moveToPreviousField) {
                Image(systemName: &amp;quot;chevron.up.circle.fill&amp;quot;)
            }
            .padding(.leading)

            Button(action: moveToNextField) {
                Image(systemName: &amp;quot;chevron.down.circle.fill&amp;quot;)
            }
            .padding(.trailing)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These buttons are placed in the &lt;code&gt;.keyboard&lt;/code&gt; toolbar, which is displayed above the keyboard when a text field is active. By default, the buttons are placed on opposite sides of the toolbar. To move them together or to one side, we can wrap them in an &lt;code&gt;HStack&lt;/code&gt; combined with a &lt;code&gt;Spacer&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Handling the focus transitions&lt;/h3&gt;&lt;p&gt;Now, we can add the logic for moving between fields:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private let fields: [Field] = [.name, .address, .mail, phone]

private func moveToPreviousField() {
    if let currentIndex = focusedField.flatMap({ fields.firstIndex(of: $0) }) {
        let previousIndex = (currentIndex - 1 + fields.count) % fields.count
        focusedField = fields[previousIndex]
    }
}

private func moveToNextField() {
    if let currentIndex = focusedField.flatMap({ fields.firstIndex(of: $0) }) {
        let nextIndex = (currentIndex + 1) % fields.count
        focusedField = fields[nextIndex]
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach will automatically scale when more textfields are added in the future. The modulo operation (%) is a way to ensure that the index stays within the bounds of the array, even when navigating past the first or last element.&lt;/p&gt;</content:encoded><category>swiftui</category><category>ux</category><category>ios</category></item><item><title><![CDATA[Understanding toolbars in SwiftUI]]></title><description><![CDATA[Learn how to add toolbar items in SwiftUI views across various placements like navigation bars, bottom bars and more.]]></description><link>https://tanaschita.com/swiftui-toolbars-guide</link><guid isPermaLink="false">https://tanaschita.com/swiftui-toolbars-guide</guid><pubDate>Sun, 01 Jun 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;toolbar()&lt;/code&gt; modifier allows us to add items to various toolbars, such as the navigation bar, bottom bar, and even above the keyboard or in modal views. Depending on the configuration of the &lt;code&gt;ToolbarItem&lt;/code&gt; and the platform, the system places the items accordingly.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore how to use the &lt;code&gt;toolbar()&lt;/code&gt; modifier in SwiftUI and how to place toolbar items in different parts of our app&amp;#x27;s UI. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Toolbar items in the bottom bar&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.bottomBar&lt;/code&gt; placement is commonly used for actions that need to be available at the bottom of the screen, such as navigation controls, context actions, or any items that should be easily accessible when the user is at the bottom of the interface.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s an example of how to add toolbar items to the bottom bar:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    NavigationStack {
        Text(&amp;quot;Hello, world!&amp;quot;)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button(&amp;quot;action1&amp;quot;, action: {})
                }
                ToolbarItem(placement: .bottomBar) {
                    Button(&amp;quot;action2&amp;quot;, action: {})
                }
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above example results in the following:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8df2e1418a33a3fde93654de2b2bdf72/2c3e9/toolbar_bottom_bar.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:61.06666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAh0lEQVQoz83RYQvEIAgG4P3/H6uxUa7Oeg+lYBwbB1sfJkgS9WS4MDN+k4gQQoCIQFUxIufs+5Z25uzucgXaWmt1qLXmaWEPXGF/wdHdESylvBA8xhQwpeQDmgbaOup3fnnaUGrtYEcfgx9VmDM6tDp3kO6AvCVsUXzCuwjWuIPXCGYC0Tn4BY0hmkK8XxrPAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Toolbar items in bottom bar&quot; title=&quot;Toolbar items in bottom bar&quot; src=&quot;/static/8df2e1418a33a3fde93654de2b2bdf72/2e9ed/toolbar_bottom_bar.png&quot; srcSet=&quot;/static/8df2e1418a33a3fde93654de2b2bdf72/12d18/toolbar_bottom_bar.png 375w,/static/8df2e1418a33a3fde93654de2b2bdf72/44507/toolbar_bottom_bar.png 750w,/static/8df2e1418a33a3fde93654de2b2bdf72/2e9ed/toolbar_bottom_bar.png 1500w,/static/8df2e1418a33a3fde93654de2b2bdf72/e90d0/toolbar_bottom_bar.png 2250w,/static/8df2e1418a33a3fde93654de2b2bdf72/9d489/toolbar_bottom_bar.png 3000w,/static/8df2e1418a33a3fde93654de2b2bdf72/2c3e9/toolbar_bottom_bar.png 4128w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar items in bottom bar.&lt;/div&gt;&lt;p&gt;If only one toolbar item is added, it will automatically be centered.&lt;/p&gt;&lt;h3&gt;Toolbar items in the navigation bar&lt;/h3&gt;&lt;h4&gt;&lt;code&gt;.principal&lt;/code&gt; placement&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;.principal&lt;/code&gt; placement allows us to place a toolbar item in the center of the navigation bar, often used for search fields or custom titles.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    NavigationStack {
        contentView
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .principal) {
                SearchView(text: $viewModel.searchText)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This places a search view in the center of the navigation bar, replacing the title.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/c573a8c328b990c87fbb2fb8a22acfd8/bd251/toolbar_principal.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:18.133333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAwklEQVQY023PPU/DMBCA4f56EhASID5Edxak/gskPrZKnQtJp9LkznEvtlzS8qKaiZbh1Q2+eySPnHPsUxXMjBg3zOcVISR2OxiG76O2WzCLTKczlssm36lqdkaHYOeNqq75XK0QUdpWDlLUdTRNS/VR45zH+/V/oOaHvo+YD3RihBBJafOnGBMqgvU96WvI+851x6CIZOD55Y3z+5Lx5JrbuzFlcUpRlL/zpOTy4oqn1xmPDxNuijPq9wXr/ZdFMvgDP5Qk88o0G54AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;ToolbarItem with a .principal placement.&quot; title=&quot;ToolbarItem with a .principal placement.&quot; src=&quot;/static/c573a8c328b990c87fbb2fb8a22acfd8/2e9ed/toolbar_principal.png&quot; srcSet=&quot;/static/c573a8c328b990c87fbb2fb8a22acfd8/12d18/toolbar_principal.png 375w,/static/c573a8c328b990c87fbb2fb8a22acfd8/44507/toolbar_principal.png 750w,/static/c573a8c328b990c87fbb2fb8a22acfd8/2e9ed/toolbar_principal.png 1500w,/static/c573a8c328b990c87fbb2fb8a22acfd8/bd251/toolbar_principal.png 1963w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar item with a .principal placement.&lt;/div&gt;&lt;h4&gt;&lt;code&gt;.navigationBarTrailing&lt;/code&gt; and &lt;code&gt;navigationBarLeading&lt;/code&gt; placement&lt;/h4&gt;&lt;p&gt;The &lt;code&gt;.navigationBarTrailing&lt;/code&gt; and &lt;code&gt;.navigationBarLeading&lt;/code&gt; placements place the toolbar items in the trailing or leading edge of the navigation bar.&lt;/p&gt;&lt;p&gt;They replace the deprecated &lt;code&gt;.navigationBarItems()&lt;/code&gt; modifier which was used before for the same purpose.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var body: some View {
    NavigationStack {
        contentView
        .navigationTitle(&amp;quot;Title&amp;quot;)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                SortButton()
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                FilterButton()
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Resulting in:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a1012341569e2cff1189a100930e8fd6/c3dd5/toolbar_navigationbartrailing.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:16%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAh0lEQVQI14WM2w6CMBQE+f8fVIwKsU0qt/YUqgKtjJEETXxxk8nuy07mnGPDWkvfD4TwoCwV8/xkmtLaKcE4RmKCcBvZH050VhCR9bc5sl+h954Ylw/LAsNwR2vDO8H1mF1OlR/xrcX9E4p4nAuYuuFatxjToJShKNS620bQhUKfL9ju+9uEL72b4+vKewrNAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Toolbar items with a .navigationBarTrailing placement.&quot; title=&quot;Toolbar items with a .navigationBarTrailing placement.&quot; src=&quot;/static/a1012341569e2cff1189a100930e8fd6/2e9ed/toolbar_navigationbartrailing.png&quot; srcSet=&quot;/static/a1012341569e2cff1189a100930e8fd6/12d18/toolbar_navigationbartrailing.png 375w,/static/a1012341569e2cff1189a100930e8fd6/44507/toolbar_navigationbartrailing.png 750w,/static/a1012341569e2cff1189a100930e8fd6/2e9ed/toolbar_navigationbartrailing.png 1500w,/static/a1012341569e2cff1189a100930e8fd6/c3dd5/toolbar_navigationbartrailing.png 2159w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar items with a .navigationBarTrailing placement.&lt;/div&gt;&lt;p&gt;Changing the placement to &lt;code&gt;.navigationBarLeading&lt;/code&gt; places the item in the leading edge of the navigation bar.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1713ad83222b3b1945e816ba60a8bfb3/98ed7/toolbar_navigationbarleading.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:16%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAiElEQVQI132MwQqCUBRE/f/fkbQ2hklUuItWgVLtBN993lciWnaiAjOCBoYZGOZ41lrGFjHUdUNRlBipaNsbTdMN2XU9xlRk2QFVh4h8/b0foLWoVSQ/MZaIDr2/3inzI07P/4Hv8WllkcTM1xFRFBMGM3x/QhhM2e72bJYpabLCucvrI/IBPgAfFeBGMt50NwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Toolbar items with a .navigationBarLeading placement.&quot; title=&quot;Toolbar items with a .navigationBarLeading placement.&quot; src=&quot;/static/1713ad83222b3b1945e816ba60a8bfb3/2e9ed/toolbar_navigationbarleading.png&quot; srcSet=&quot;/static/1713ad83222b3b1945e816ba60a8bfb3/12d18/toolbar_navigationbarleading.png 375w,/static/1713ad83222b3b1945e816ba60a8bfb3/44507/toolbar_navigationbarleading.png 750w,/static/1713ad83222b3b1945e816ba60a8bfb3/2e9ed/toolbar_navigationbarleading.png 1500w,/static/1713ad83222b3b1945e816ba60a8bfb3/e90d0/toolbar_navigationbarleading.png 2250w,/static/1713ad83222b3b1945e816ba60a8bfb3/98ed7/toolbar_navigationbarleading.png 2375w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar item with a .navigationBarLeading placement.&lt;/div&gt;&lt;p&gt;When placing multiple toolbar items with the same placement, we can use a &lt;code&gt;ToolbarItemGroup&lt;/code&gt; to avoid repetition.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.toolbar {
    ToolbarItemGroup(placement: .navigationBarTrailing) {
        SortButton()
        FilterButton()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Toolbar items above keyboard&lt;/h3&gt;&lt;p&gt;Another toolbar item placement possibility is &lt;code&gt;.keyboard&lt;/code&gt; which places items above the keyboard when presented. This is especially useful for adding &amp;quot;Previous&amp;quot; and &amp;quot;Next&amp;quot; buttons when interacting with text fields.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/313cb25507751a1c2c77cacb862724ee/a0bd0/toolbar_keyboard.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:37.6%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABEUlEQVQoz5WRv0/CQBxH+btNjJOLu6MDgwxumkiiUUmDDgjECPJLoLS0vWt71/Zqoo7PUBIREgMMb3v38vnmSlmW8Zc0TTF5jpz4WLcXnJUPODk65K5S5vL0mKvzCp9f3yRJwubbBaX12Cqamg9EHGF7U0bDEWEU4nsu87m3ctJ0W3ApxLFiPHGwZx6OK3E9ycwJmLlLlNZr/tag1ho/kASBJIwUQobESiOEJIriX2+PYIKQURHdpDcYFxcYs2PQGIOQkla7w3jqFHE/EMWyt/471RuLQEjy3OwWXIiLJdfVGu2XHkqn2LaLTjKa7S41q7HnwiwjihXNVodef4JVb3D/8Ej96Zn+YMprd4hS/3/KDxkcTvEMuQqTAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Toolbar items above keyboard.&quot; title=&quot;Toolbar items above keyboard.&quot; src=&quot;/static/313cb25507751a1c2c77cacb862724ee/2e9ed/toolbar_keyboard.png&quot; srcSet=&quot;/static/313cb25507751a1c2c77cacb862724ee/12d18/toolbar_keyboard.png 375w,/static/313cb25507751a1c2c77cacb862724ee/44507/toolbar_keyboard.png 750w,/static/313cb25507751a1c2c77cacb862724ee/2e9ed/toolbar_keyboard.png 1500w,/static/313cb25507751a1c2c77cacb862724ee/e90d0/toolbar_keyboard.png 2250w,/static/313cb25507751a1c2c77cacb862724ee/9d489/toolbar_keyboard.png 3000w,/static/313cb25507751a1c2c77cacb862724ee/a0bd0/toolbar_keyboard.png 3388w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar items above keyboard.&lt;/div&gt;&lt;h3&gt;Toolbar items in modal views&lt;/h3&gt;&lt;p&gt;Further placement options are &lt;code&gt;.confirmationAction&lt;/code&gt;, &lt;code&gt;.destructiveAction&lt;/code&gt; and &lt;code&gt;.cancellationAction&lt;/code&gt; that we can use in toolbars presented by a modal view, for example when using the &lt;code&gt;sheet()&lt;/code&gt; modifier.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    NavigationStack {
        Text(&amp;quot;Sheet&amp;quot;)
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button(&amp;quot;Confirm&amp;quot;, action: {})
            }
            ToolbarItem(placement: .destructiveAction) {
                Button(&amp;quot;Delete&amp;quot;, action: {})
            }
            ToolbarItem(placement: .cancellationAction) {
                Button(&amp;quot;Cancel&amp;quot;, action: {})
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Resulting in:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/c698d9201f4a0eba7ad28cd9ddd71f59/6048d/toolbar_sheet.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:18.666666666666668%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAl0lEQVQY03WOQQqFIBRFbQdhq20XLSP5o/gY5NDtRDqwJurrfnxgFPGFyxUv56Dw3qNkXVcQEfq+hxACUkrue5qm4W7bFrLrMAwDM4WtHlEv27Zh33cYY6DGEWZZoLXGPM9XvtPEKdtHKVhrcRwHsy9hlVLO+HtiBOpOhBTjQ/YSOuf4l3SeyAVICTnnK+W9bqVDCMzchT8icg3Qph+i4AAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;ToolbarItems in modal view.&quot; title=&quot;ToolbarItems in modal view.&quot; src=&quot;/static/c698d9201f4a0eba7ad28cd9ddd71f59/2e9ed/toolbar_sheet.png&quot; srcSet=&quot;/static/c698d9201f4a0eba7ad28cd9ddd71f59/12d18/toolbar_sheet.png 375w,/static/c698d9201f4a0eba7ad28cd9ddd71f59/44507/toolbar_sheet.png 750w,/static/c698d9201f4a0eba7ad28cd9ddd71f59/2e9ed/toolbar_sheet.png 1500w,/static/c698d9201f4a0eba7ad28cd9ddd71f59/e90d0/toolbar_sheet.png 2250w,/static/c698d9201f4a0eba7ad28cd9ddd71f59/6048d/toolbar_sheet.png 2305w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Toolbar items in modal view.&lt;/div&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Learn to build adaptive layouts with SwiftUI's ViewThatFits container.]]></title><description><![CDATA[Understand how SwiftUI's ViewThatFits container works.]]></description><link>https://tanaschita.com/swiftui-viewthatfits-container</link><guid isPermaLink="false">https://tanaschita.com/swiftui-viewthatfits-container</guid><pubDate>Sun, 25 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;ViewThatFits&lt;/code&gt; container helps us build adaptive layouts by allowing us to provide multiple views while automatically picking the first one which fits into available space.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump into an example to see how the container works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Consider a scenario where you need to display a list of messages. If a message fits within a single line, we just display it fully. If it doesn&amp;#x27;t fit, we want to show a truncated version and a &amp;quot;more&amp;quot; button to open the full message. Something like this:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:962px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/68f99aa520fb9371eb1bbdd3d3086b94/21cd7/example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:20.8%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAwklEQVQY03WPSw6CQBBEOYYDXMu4IC40no+NcAUCnIAQHD7OhkRHEsJHfmV6iG7USirT6c5U99N2+xN0w4TruCC1bYuu69D3/cfzPGNZli9P06T+BEGADWPYWkdoFGiYJmz7DM4BIa7IsgxlWSJNUyRJgn+iRaQw/BnogF8AKe+I4xiccwghVGAURSiKQvXyPFdzKW+oqieqB+B5PhjTsbUOa6BCdldkQmyaRpnQ6a3rWtVvU28cRwzDiuz7dKGuLnwBwOELxDh/qn4AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;example&quot; title=&quot;example&quot; src=&quot;/static/68f99aa520fb9371eb1bbdd3d3086b94/21cd7/example.png&quot; srcSet=&quot;/static/68f99aa520fb9371eb1bbdd3d3086b94/12d18/example.png 375w,/static/68f99aa520fb9371eb1bbdd3d3086b94/44507/example.png 750w,/static/68f99aa520fb9371eb1bbdd3d3086b94/21cd7/example.png 962w&quot; sizes=&quot;(max-width: 962px) 100vw, 962px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;As we can see above, the first message fits into the available space, so we don&amp;#x27;t need the button. The second line only shows a part of the message with a button to show the full message.&lt;/p&gt;&lt;p&gt;To achieve this result, the &lt;code&gt;ViewThatFits&lt;/code&gt; comes in handy. We just need to provide two different views, one that only contains the text and one that contains the text and the button. The &lt;code&gt;ViewThatFits&lt;/code&gt; container will automatically pick the right one for us.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    VStack(alignment: .leading, spacing: 10) {
        ForEach(messages, id: \.self) { message in
            HStack() {
                Image(systemName: &amp;quot;info.circle.fill&amp;quot;)
                ViewThatFits {
                    Text(message)
                        .fixedSize()
                    HStack {
                        Text(message)
                            .lineLimit(1)
                        Button(&amp;quot;more&amp;quot;) {
                            // Present full message
                        }
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The first view is a &lt;code&gt;Text&lt;/code&gt; view with the &lt;code&gt;.fixedSize()&lt;/code&gt; modifier, ensuring the text is not truncated.&lt;/li&gt;&lt;li&gt;The second view is an &lt;code&gt;HStack&lt;/code&gt; containing a truncated &lt;code&gt;Text&lt;/code&gt; view and a &amp;quot;more&amp;quot; &lt;code&gt;Button&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;code&gt;ViewThatFits&lt;/code&gt; evaluates these views in order and selects the first one that fits within the available space. If the first view doesn&amp;#x27;t fit, it proceeds to the next one. This approach allows for dynamic and responsive layouts.&lt;/p&gt;&lt;p&gt;&lt;code&gt;ViewThatFits&lt;/code&gt; determines which view to display based on the &amp;quot;ideal size&amp;quot; of each child view. The ideal size refers to the size a view would occupy if it had unlimited space. By default, &lt;code&gt;ViewThatFits&lt;/code&gt; considers both horizontal and vertical axes when evaluating the ideal size. However, we can specify which axis to consider by using the &lt;code&gt;in:&lt;/code&gt; parameter. In our case, we are looking for a horizontal evaluation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ViewThatFits(in: .horizontal) {
    Text(message)
        .fixedSize()
    HStack {
        Text(message)
            .lineLimit(1)
        Button(&amp;quot;more&amp;quot;) {
            // Present full message
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, &lt;code&gt;ViewThatFits&lt;/code&gt; will only consider the horizontal axis when determining which view to display.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding LLDB print commands for iOS debugging with Xcode]]></title><description><![CDATA[Learn the difference between po, p, and v to inspect variables in Xcode's console.]]></description><link>https://tanaschita.com/lldb-understanding-print-commands</link><guid isPermaLink="false">https://tanaschita.com/lldb-understanding-print-commands</guid><pubDate>Sun, 18 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;LLDB is a powerful tool for exploring and debugging iOS applications at runtime.&lt;/p&gt;&lt;p&gt;When we set a breakpoint in our code, Xcode pauses the app and gives us a few ways to inspect the current state. We can either use the variable viewer in Xcode&amp;#x27;s sidebar or enter LLDB commands directly in the console.&lt;/p&gt;&lt;p&gt;To inspect variables through LLDB, we typically use three print commands:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;v&lt;/code&gt; for direct memory inspection&lt;/li&gt;&lt;li&gt;&lt;code&gt;p&lt;/code&gt; for evaluated expressions&lt;/li&gt;&lt;li&gt;&lt;code&gt;po&lt;/code&gt; for object descriptions&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s walk through how each one works and when to use them.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;LLDB command v&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;v&lt;/code&gt; command is an alias for &lt;code&gt;frame variable&lt;/code&gt;. It reads what&amp;#x27;s currently in memory and prints an LLDB-formatted summary of the value. Xcode uses this command behind the scenes for its variable viewer.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;(lldb) v bird

(ExampleApp.Bird) bird = {
  name = &amp;quot;kea&amp;quot;
  conservationStatus = en
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command is fast and safe because it doesn&amp;#x27;t compile or evaluate any expressions. It only works with stored properties - computed properties or function calls will result in an error:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;(lldb) v bird.conservationStatus.title

error: &amp;quot;title&amp;quot; is not a member of &amp;quot;(ExampleApp.ConservationStatus) bird.conservationStatus&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;LLDB command p&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;p&lt;/code&gt; command is an alias for &lt;code&gt;expression&lt;/code&gt;. Unlike &lt;code&gt;v&lt;/code&gt;, it compiles and executes code on the spot, which means we can evaluate expressions, access computed properties, and call functions.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;(lldb) p bird

(ExampleApp.Bird) $R2 = {
  name = &amp;quot;kea&amp;quot;
  conservationStatus = en
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a computed variable:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(lldb) p bird.conservationStatus.title
(String) $R3 = &amp;quot;endangered&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;LLDB command po&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;po&lt;/code&gt; command is also an evaluated expression, but it prints a human-readable description of the object instead of the LLDB-formatted structure. Internally, it calls &lt;code&gt;expression --object-description&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;If the object conforms to &lt;code&gt;CustomDebugStringConvertible&lt;/code&gt;, po uses the custom debug description we provide.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension Bird: CustomDebugStringConvertible {
    var debugDescription: String {
        return &amp;quot;The bird \(name) is \(conservationStatus.title)&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which results in the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;(lldb) po bird

▿ The bird kea is endangered
  - name : &amp;quot;kea&amp;quot;
  - conservationStatus : ExampleApp.ConservationStatus.en
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If no custom description is provided, &lt;code&gt;po&lt;/code&gt; falls back to the system’s default.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;In most cases, using &lt;code&gt;p&lt;/code&gt; or &lt;code&gt;po&lt;/code&gt; gives us the most flexibility. They evaluate expressions and work with computed properties and methods. However, they do add a bit of runtime overhead since they compile and execute code.&lt;/p&gt;&lt;p&gt;If we&amp;#x27;re dealing with timing-sensitive code, such as multithreading bugs or race conditions, it may be better to use the &lt;code&gt;v&lt;/code&gt; command. It doesn&amp;#x27;t disturb the execution environment as much because it doesn&amp;#x27;t evaluate code, just reads memory.&lt;/p&gt;&lt;p&gt;For these situations, we can even set breakpoints that don&amp;#x27;t pause the app. In Xcode&amp;#x27;s breakpoint editor, we can enable the option to &lt;code&gt;Automatically continue after evaluating actions&lt;/code&gt; and log the output with &lt;code&gt;v&lt;/code&gt;, so we can debug without affecting the timing.&lt;/p&gt;</content:encoded><category>lldb</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Scheduling notifications with time, calendar, and location triggers in iOS]]></title><description><![CDATA[Learn how to schedule local notifications using different trigger types with the UserNotifications framework.]]></description><link>https://tanaschita.com/ios-local-notification-triggers</link><guid isPermaLink="false">https://tanaschita.com/ios-local-notification-triggers</guid><pubDate>Sun, 11 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;UserNotifications&lt;/code&gt; framework provides several flexible ways to schedule local notifications, depending on when and how we want them to be delivered.&lt;/p&gt;&lt;p&gt;In this guide, we&amp;#x27;ll walk through the three main types of triggers available for local notifications:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Time interval triggers&lt;/strong&gt; – fire after a set number of seconds&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Calendar triggers&lt;/strong&gt; – fire at a specific date and time&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Location triggers&lt;/strong&gt; – fire when the user enters or exits a geographic region&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Time interval trigger&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;time interval trigger&lt;/strong&gt; is the simplest option. It fires after a fixed amount of time has passed, making it perfect for short-term reminders or countdowns. For example, to trigger a notification 10 minutes from now, we would use:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10 * 60, repeats: false)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Calendar trigger&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;calendar trigger&lt;/strong&gt; allows us to schedule notifications based on specific dates and times or repeating intervals. This is ideal for recurring reminders, like daily health checks or weekly meeting reminders. For example, we can trigger a notification every morning at 7:30 with the following code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var dateComponents = DateComponents()
dateComponents.hour = 7
dateComponents.minute = 30
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This trigger provides a lot of flexibility, as we can match on any combination of date components, like weekday, month, or year, to precisely control when notifications are delivered.&lt;/p&gt;&lt;h3&gt;Location trigger&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;location trigger&lt;/strong&gt; is a more context-aware option that fires when a user enters or exits a specified geographic region. For example, we can trigger a notification when a user exits a 20 km radius around Leipzig&amp;#x27;s city center:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import CoreLocation

let center = CLLocationCoordinate2D(latitude: 51.3396955, longitude: 12.3730747)
let region = CLCircularRegion(center: center, radius: 20 * 1000, identifier: &amp;quot;leipzig&amp;quot;)
region.notifyOnEntry = false
region.notifyOnExit = true

let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the &lt;code&gt;notifyOnEntry&lt;/code&gt; and &lt;code&gt;notifyOnExit&lt;/code&gt; properties, we can control whether the app should deliver notifications when the user enters, exits, or both. Keep in mind that location triggers require location permissions and can be affected by power-saving modes.&lt;/p&gt;</content:encoded><category>notifications</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Working with Xcode configuration files]]></title><description><![CDATA[Learn how to manage different iOS build environments with .xcconfig files.]]></description><link>https://tanaschita.com/xcode-configuration-files</link><guid isPermaLink="false">https://tanaschita.com/xcode-configuration-files</guid><pubDate>Sun, 04 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article on &lt;a href=&quot;/xcode-build-environments/&quot;&gt;creating and managing multiple app environments in Xcode&lt;/a&gt;, we learned, how to set up environments such as debug, staging, and release when developing an iOS app.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore an approach by using Xcode configuration files (&lt;code&gt;.xcconfig&lt;/code&gt;) to define environment-specific values.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Creating configuration files&lt;/h3&gt;&lt;p&gt;The first step is to create a &lt;code&gt;.xcconfig&lt;/code&gt; file for each build configuration by selecting &lt;strong&gt;File → New → File&lt;/strong&gt; and using the &lt;strong&gt;Configuration Settings File&lt;/strong&gt; template.&lt;/p&gt;&lt;p&gt;For example, we could create:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Debug.xcconfig&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Staging.xcconfig&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Release.xcconfig&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By default, each file starts empty.&lt;/p&gt;&lt;h3&gt;Connecting configuration files to build configurations&lt;/h3&gt;&lt;p&gt;We can activate the configuration files in the Info tab of our project:&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/9090690c3dc219a6b22b7c1232fcd09a/eda80/activate-configuration-files.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:36.8%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABcElEQVQoz02SSW7bQBBFeYTAXgSwJbFHdoujxUFDSEoRmEBAEmTYJSfIIhvb8B1y7BewhQBePBTQ9VFdv6oiayVxvESIJVKucM5SlBlp6lFKoLVAG0XR9eRFTlVmeG9CnMkyHzRKX7XReZrY7zuqqkBriXeKbfuAdxop7lFygRQLZHx/RSmUsQFtHcaIoAsasSDqxyPbrqEss9CRPf/Gff/L29MLt/0TN+8eQ7wdnnlzeCQffzF8/MlD/wPffuZu+MPN8HzV9E9EbVPhnGG1ukOKJbFO2I0Tn758ZXvo0YlHWYdOHEIb6u3IdPlGf7rgyx3SzvkkaGai0/sjl8sHmmYTZjlbbbuaw76j73fkWYKSS4yOg6WiyBiPA8PY03YNRouQ+0/kvWW9TgLzYtI0oa4r8jwlnd/TDOuS4GJemLWaJLlijESpOIzqGmOiuchrjFszHkem6cwwHDgMR+q2o6krNpsydGitCh/MBefLeM0/msLT0Rc8xWQAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Connecting the configuration files&quot; title=&quot;Connecting the configuration files&quot; src=&quot;/static/9090690c3dc219a6b22b7c1232fcd09a/2e9ed/activate-configuration-files.png&quot; srcSet=&quot;/static/9090690c3dc219a6b22b7c1232fcd09a/12d18/activate-configuration-files.png 375w,/static/9090690c3dc219a6b22b7c1232fcd09a/44507/activate-configuration-files.png 750w,/static/9090690c3dc219a6b22b7c1232fcd09a/2e9ed/activate-configuration-files.png 1500w,/static/9090690c3dc219a6b22b7c1232fcd09a/eda80/activate-configuration-files.png 1590w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Connecting configuration files.&lt;/div&gt;&lt;/undefined&gt;&lt;p&gt;Here, we can select the corresponding &lt;code&gt;.xcconfig&lt;/code&gt; file for each configuration.&lt;/p&gt;&lt;h3&gt;Adding values to configuration files&lt;/h3&gt;&lt;p&gt;Now, we can define values such as feature flags or app display names. For example:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Debug.xcconfig&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;APP_DISPLAY_NAME = ExampleApp Debug
FEATURE_X_ENABLED = YES
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Staging.xcconfig&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;APP_DISPLAY_NAME = ExampleApp Staging
FEATURE_X_ENABLED = YES
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Release.xcconfig&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;APP_DISPLAY_NAME = ExampleApp
FEATURE_X_ENABLED = NO
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;💡 Note: Don&amp;#x27;t use quotation marks for strings, even if they contain spaces. Values like &lt;code&gt;YES&lt;/code&gt; and &lt;code&gt;NO&lt;/code&gt; are treated as strings. Also, Xcode may interpret &lt;code&gt;//&lt;/code&gt; in &lt;code&gt;http://&lt;/code&gt; as a comment—use &lt;code&gt;https:/$()/dev.api.com&lt;/code&gt; as a workaround.&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;Accessing configuration values in code&lt;/h3&gt;&lt;p&gt;To read these values in code, we first reference them in our &lt;code&gt;Info.plist&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;FEATURE_X_ENABLED&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;$(FEATURE_X_ENABLED)&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then access them in Swift:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Bundle.main.object(forInfoDictionaryKey: &amp;quot;FEATURE_X_ENABLED&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>xcode</category><category>ios</category><category>swift</category></item><item><title><![CDATA[Creating and managing multiple app environments in Xcode]]></title><description><![CDATA[Learn how to add and configure debug, staging, and release environments in your iOS app using Xcode.]]></description><link>https://tanaschita.com/xcode-build-environments</link><guid isPermaLink="false">https://tanaschita.com/xcode-build-environments</guid><pubDate>Sat, 03 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After getting on overview on &lt;a href=&quot;/xcode-targets-schemes/&quot;&gt;Xcode&amp;#x27;s build configuration concepts&lt;/a&gt;, we can take the next step: adding and managing multiple environments like debug, staging, and release.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s walk through the process step by step.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding a new build configuration&lt;/h3&gt;&lt;p&gt;Xcode already provides &lt;code&gt;Debug&lt;/code&gt; and &lt;code&gt;Release&lt;/code&gt; configurations by default. To add a custom one (like &lt;code&gt;Staging&lt;/code&gt;), we open the &lt;strong&gt;project editor&lt;/strong&gt;, go to the &lt;strong&gt;Info&lt;/strong&gt; tab, and click the &lt;code&gt;+&lt;/code&gt; button in the &lt;strong&gt;Configurations&lt;/strong&gt; section.&lt;/p&gt;&lt;p&gt;We can duplicate the &lt;code&gt;Debug&lt;/code&gt; configuration and name the new one &lt;code&gt;Staging&lt;/code&gt;.&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1146px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/03d356b1cfb99a41edfc58a3c2490af9/601e5/add-config.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:36.53333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABOklEQVQoz12RW26DMBBFvZWU8IqB4gRsYwgBQoKQkiZq+9MF9CF1C/3uJtpKlbrQG+EAavJx5BnfmdHoDvGnNpaxwDYv0awqcD8EvTERmDY67RrPsLQ+vEM86KRLAstFFgn9QcdC6xLDwmwyBXM9ZAsOGS50Txad46GPdEFTrHFod2AzH3MaXOH3BAhdiiWXuN8d8LA/4PHuiH3TQrJorCXddpuiQFOWSOMIGY9HUs6RigSpkGe4RK5SbMoKbb1FU9VY5wUUF2MN8U0HvhvBdWLM/tHl1Gbweo9Get/oxLzwcIBQw0H59Inm7Rfb1x/Uz99oXr7Qvv9BHj+gGMNKpMhjiZVQiP0Q3tRGYDqa66MRzzCxFAqVylAphSpRKKVCneXa8JwnvfFzJOFCH0DczjUJixDRQG89DDwBz2rgE7kFYksAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Adding new configuration&quot; title=&quot;Adding new configuration&quot; src=&quot;/static/03d356b1cfb99a41edfc58a3c2490af9/601e5/add-config.png&quot; srcSet=&quot;/static/03d356b1cfb99a41edfc58a3c2490af9/12d18/add-config.png 375w,/static/03d356b1cfb99a41edfc58a3c2490af9/44507/add-config.png 750w,/static/03d356b1cfb99a41edfc58a3c2490af9/601e5/add-config.png 1146w&quot; sizes=&quot;(max-width: 1146px) 100vw, 1146px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Adding a new configuration.&lt;/div&gt;&lt;/undefined&gt;&lt;h3&gt;Creating schemes for each environment&lt;/h3&gt;&lt;p&gt;To easily run and test each configuration, we can create separate schemes by opening &lt;strong&gt;Manage Schemes&lt;/strong&gt; from the scheme dropdown and duplicate the default scheme for each environment we want to support (e.g. &lt;code&gt;Staging&lt;/code&gt;, &lt;code&gt;Release&lt;/code&gt;).&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d98e7c14847961c80e902d678740fb77/b750f/manage-schemes.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:27.200000000000003%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABIUlEQVQY003L30+CUADFcf6KWlMBL+AFvAqCFxCRH9pqlk5ba63eWmu99P+/fls8uB4+2znbOcYir9hsanaHF9rDG/X9iWb/jC4bllqz3Xbsdlvquu5VVUWeZ2SZRmtNURR0Xdvv2rbFCKMEpUJUcUvcPaG7I0l7JMpqstWaRbJA+hJv4uF6HpY1wrbNi79umsMLI1AzwtBn4lqYgyus0Q2jwTUqdKnXS1bZHBWMCbwRoZqiopg0jVAqII7npGlMksRI6SKEhTGdzVhXJdNpQBBIHHeMLx3y5pH96w/F3TsifUDoE85ihxib/fE/x7Ev2QjnCZu2o6krlssE1xX4UjBbn5HdN7L5xCo/GKy+MNMzwh7ieg55rntlWRBFCiHs3i9XU5yDZVgTyAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Managing schemes in Xcode.&quot; title=&quot;Managing schemes in Xcode.&quot; src=&quot;/static/d98e7c14847961c80e902d678740fb77/2e9ed/manage-schemes.png&quot; srcSet=&quot;/static/d98e7c14847961c80e902d678740fb77/12d18/manage-schemes.png 375w,/static/d98e7c14847961c80e902d678740fb77/44507/manage-schemes.png 750w,/static/d98e7c14847961c80e902d678740fb77/2e9ed/manage-schemes.png 1500w,/static/d98e7c14847961c80e902d678740fb77/b750f/manage-schemes.png 1546w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Managing schemes in Xcode.&lt;/div&gt;&lt;/undefined&gt;&lt;p&gt;💡 It&amp;#x27;s usually best to duplicate the existing scheme. This copies over all relevant settings (like build, test, and archive actions), so we only need to change the build configuration. Creating a scheme from scratch is also possible, but it requires manual setup and is better suited for workflows which are structurally different.&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a5113db2ce3e7d70163a53c2785a976b/968c3/duplicate-scheme.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:47.199999999999996%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABaUlEQVQoz42STW/TQBBAc+eASKmdrNfFieMkduz9ckuqlliQFlEO5R8g7tCo///0kHdNW6BIHJ5mZ3b0NKPdUZa9IZCRJIJExJSqpbm8pe1uyc2eTAdm5gqz+8Lm7UcKF/KZ2bM6+4y5uCHP54x6iZSJZyoEUhyzbj8w39/T3BzI93fI7gfpwPL6wObTgdX13VD7zsn7A0X3jUWRM+pFaSo9QkxJkila1VhVouvSR6dC7DFNiW5CfLhrSpyuKZ4K5YNwglIKYyz6H5iBx5rBWve7MJV/Cg3GaLT+P4JwEYRyWDkRwgubpsZa6xuD+G/atsU550XWaJyqKBbZ8xNut2fsdpecn2+pqpLVqvDrPGW9Lnx9WcxY1obq6itZ5R4nlINwMjlmPs84PXV03Ts2mzVpmhDHr/3dL/o87s/xEXGSIqoLJic5o745fBvhX7hvHo9folSNc9ZP0fdE0VGQPEc0Jhq/II5e8RM4CQjmQjamkQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Duplicating a scheme in Xcode.&quot; title=&quot;Duplicating a scheme in Xcode.&quot; src=&quot;/static/a5113db2ce3e7d70163a53c2785a976b/2e9ed/duplicate-scheme.png&quot; srcSet=&quot;/static/a5113db2ce3e7d70163a53c2785a976b/12d18/duplicate-scheme.png 375w,/static/a5113db2ce3e7d70163a53c2785a976b/44507/duplicate-scheme.png 750w,/static/a5113db2ce3e7d70163a53c2785a976b/2e9ed/duplicate-scheme.png 1500w,/static/a5113db2ce3e7d70163a53c2785a976b/968c3/duplicate-scheme.png 2030w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Duplicating a scheme in Xcode.&lt;/div&gt;&lt;/undefined&gt;&lt;p&gt;After duplicating, we rename the schemes accordingly and edit each one to use the matching build configuration for &lt;strong&gt;Build&lt;/strong&gt;, &lt;strong&gt;Run&lt;/strong&gt;, &lt;strong&gt;Test&lt;/strong&gt;, and so on.&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/73df050d247185e3a96d367d91dc17cd/e557d/edit-scheme.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:56.266666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABt0lEQVQoz5WTy3LTMBSG/RoMaWzHtizb8v0m0zTQNlkEskkyDK/CghmWPAAP/DFS6zZlwoLFNz6Wfv86PufISbt7ho8HwuKWQA2M48hms6FpGqqqsjT9B9qup+s62rale47LsqSua4vRK6Vw5OcfVN9+8277i8X9d6SUyFhYcpVRFDl6/YnN3QY9Dmg9ME2aSY+UVY2UsdUmMiYKA5y0qlmvNXHkE4uAle/iuTcWEQU0dYHWIw8Pj3RtTVkocpVS5hmqqPF8D9+9wfeWuMsFTtKMPG63pGlCHAuCwLebhjDwUZkkTTP6vmfoW+qyQGUJUbiyh5oEZsy7I/OKru+QUjwZrrwXQyMyGZkaDcNgMyxyRZZIUhmTpdJqZq1rDGMREoYrhIjeGM7CcOWRJIltRFMVKJXaXzbfXepeDeMIIULM82/DWezbmi7wLtYvja4ain8Y/g/etQztxnLx0ulr+Jd4c7zAXb7HkW8MnzAzlTzPl2nWHMemziJCROFbhEAkGUEQ4AjblIAoCuzImJtyPB45nU7sdjsOhwPn85n9fs80TWityfPc3pKiLClVQnb7heLrT+Lmjj/gykbEYnY5VAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Editing a scheme.&quot; title=&quot;Editing a scheme.&quot; src=&quot;/static/73df050d247185e3a96d367d91dc17cd/2e9ed/edit-scheme.png&quot; srcSet=&quot;/static/73df050d247185e3a96d367d91dc17cd/12d18/edit-scheme.png 375w,/static/73df050d247185e3a96d367d91dc17cd/44507/edit-scheme.png 750w,/static/73df050d247185e3a96d367d91dc17cd/2e9ed/edit-scheme.png 1500w,/static/73df050d247185e3a96d367d91dc17cd/e557d/edit-scheme.png 1872w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Editing a scheme.&lt;/div&gt;&lt;/undefined&gt;&lt;h3&gt;Customizing build settings per configuration&lt;/h3&gt;&lt;p&gt;Now that our configurations are in place, we can assign different values for each. This is useful for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;App bundle identifiers&lt;/li&gt;&lt;li&gt;App icons&lt;/li&gt;&lt;li&gt;Display names&lt;/li&gt;&lt;li&gt;Version suffixes&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For example, we can adjust the bundle identifier for each configuration by searching for it in the &lt;strong&gt;build settings&lt;/strong&gt; tab:&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1492px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/365a678c0e6677c7f1f907f296c42207/7a941/edit-build-settings.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:36.53333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABZUlEQVQoz1WP626jMBSEeZFdNRgDBl+CwUCAkGzS0qTdVqs23Sjv/x5nVjiNlP3xaeSZsY4m6GxFVa5p7Roa6xVNmx3t+5EupzPtujU5VVDbH6jbvFGpHTmpyUpNfd1SXVhKGaeMxyQYJyNyCipp0NkKo2vhlMHgWgxVg7fpiL6sYYWEYhz5gkHOGsaQcQqnlzBpDhFyZFEMwTiMyBGYJKNGF9QuS9I8pWwReeIfD14li0lFqc8U/9YoIRlyUizxbx1dVbGYApMINLqAZDHEA0O2iDyKJd6TIffkd3j/O/vPDzkCZwpsuwGP4xZjs8LgGqzrFpUyKKWGzRVaW+FlOmBVOtRLi6XIvV/Ka+eeoMgVrNToXYPL6Qvvx1cMdYumKP3nmfno63TA5esvzh+fOH+c8Pn+B9t+7bNbZyaY58nwOnffj+itQ/pzAXU3y3CBbdPhZfeE4689fj9OeN7sfPc2/zb7H+gb24ekS+IrAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Customizing build settings for each configuration.&quot; title=&quot;Customizing build settings for each configuration.&quot; src=&quot;/static/365a678c0e6677c7f1f907f296c42207/7a941/edit-build-settings.png&quot; srcSet=&quot;/static/365a678c0e6677c7f1f907f296c42207/12d18/edit-build-settings.png 375w,/static/365a678c0e6677c7f1f907f296c42207/44507/edit-build-settings.png 750w,/static/365a678c0e6677c7f1f907f296c42207/7a941/edit-build-settings.png 1492w&quot; sizes=&quot;(max-width: 1492px) 100vw, 1492px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;
  Customizing build settings for each configuration.
&lt;/div&gt;&lt;/undefined&gt;&lt;h3&gt;Adding user-defined build settings&lt;/h3&gt;&lt;p&gt;For environment-specific variables such as API URLs or feature flags, we can either configure them in build settings or create a wrapper type to check the current environment in code and return custom values based on that. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct BuildConfiguration {
    enum Environment: String {
        case debug = &amp;quot;Debug&amp;quot;
        case staging = &amp;quot;Staging&amp;quot;
        case release = &amp;quot;Release&amp;quot;
    }

    let environment: Environment

    init() {
        let rawValue = Bundle.main.object(forInfoDictionaryKey: &amp;quot;Environment&amp;quot;) as? String ?? &amp;quot;Debug&amp;quot;
        environment = Environment(rawValue: rawValue) ?? .debug
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively, we can use Xcode configuration files (xcconfig) to manage large sets of build settings in one central file.&lt;/p&gt;&lt;p&gt;➡️ Continue reading: &lt;a href=&quot;/xcode-configuration-files/&quot;&gt;Working with Xcode configuration files&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>xcode</category><category>ios</category><category>swift</category></item><item><title><![CDATA[Understanding Xcode targets, schemes and build configurations]]></title><description><![CDATA[Learn the foundational concepts of Xcode build configuration and how targets, schemes, and configurations work together.]]></description><link>https://tanaschita.com/xcode-targets-schemes</link><guid isPermaLink="false">https://tanaschita.com/xcode-targets-schemes</guid><pubDate>Fri, 02 May 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we develop an iOS application, we usually need to support multiple environments like debug, staging, and release. The first step in that process is understanding how Xcode organizes our project into targets, schemes, and configurations.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s walk through these concepts together.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;When we create a new Xcode project, it automatically includes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;An app target&lt;/li&gt;&lt;li&gt;Two test targets&lt;/li&gt;&lt;li&gt;A default scheme&lt;/li&gt;&lt;li&gt;Two build configurations: &lt;code&gt;Debug&lt;/code&gt; and &lt;code&gt;Release&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/288ea7687f9dba7f4823328e9f7d800e/2a817/xcode-new-project.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:41.06666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABaklEQVQoz3VR226jMBTkM1ql4CsYsDE4ScOlCAhJtspG+7L7/98yKztppa26D6PzcMZzOY4a5zDMZ1x//cZy/YPpfMN4vuG17XE6HbGuR8zzhOMyo+87tG2Lrmuxrive33/gcrngtK4YhgFeK3JbB20b2Nc32PEn3NsFVXcClSkojUFpAi44RCohBAPnNEwPxkjY+/mByFYW2pRIJQWJn0CSZ6iMQZcKQlBwFiMrSphmC6NzFEUGYwpUlUaWCXBO7jxOglmU5yW6ccY0jWhcE5zb7oC6roKjJzGWhLRScux2Dvv99nP/FZHOi0CqaxOcpGRwzoKQlyDCWBzcPflj51wNpdLAv9enn4jKvAyJlmUKNbxQUSjs9w5NbaAri7xQKEsFpWQw8Ik97jX/RaS1QZqJcBv/gDKKenfAMo8YhhaHvsd2dxe31gRhD5/2O9FIqfzxm/GjYhJqaZ0jSTYgZAMp6CNd8rjr//EXoVbtuOtXm5MAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;A new Xcode project&quot; title=&quot;A new Xcode project&quot; src=&quot;/static/288ea7687f9dba7f4823328e9f7d800e/2e9ed/xcode-new-project.png&quot; srcSet=&quot;/static/288ea7687f9dba7f4823328e9f7d800e/12d18/xcode-new-project.png 375w,/static/288ea7687f9dba7f4823328e9f7d800e/44507/xcode-new-project.png 750w,/static/288ea7687f9dba7f4823328e9f7d800e/2e9ed/xcode-new-project.png 1500w,/static/288ea7687f9dba7f4823328e9f7d800e/2a817/xcode-new-project.png 1926w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Configuration of a new Xcode project&lt;/div&gt;&lt;p&gt;This gives us a strong starting point, but to add more environments and customize behavior, we first need to understand the roles of each component.&lt;/p&gt;&lt;h3&gt;What is a target?&lt;/h3&gt;&lt;p&gt;Xcode projects contain targets, which specify how a product is built. Each target has its own build settings and phases. Targets can override settings defined at the project level.&lt;/p&gt;&lt;p&gt;Think of a target as a blueprint for building something specific in an app project. For example, we might have one target for the main iOS app, and another for a Share Extension or a Watch app. Each target defines how that specific product is built, including source files, resources, and its own build settings.&lt;/p&gt;&lt;h3&gt;What is a scheme?&lt;/h3&gt;&lt;p&gt;An Xcode scheme defines how Xcode builds, runs, tests, archives, and profiles our app. It also controls which build configuration is used for each action.&lt;/p&gt;&lt;p&gt;We can think of a scheme like a shortcut. It tells Xcode what to build and how to run it for different actions. For example, we can create a scheme called &lt;code&gt;ExampleApp Staging&lt;/code&gt; that runs the app using a staging backend and another scheme called &lt;code&gt;ExampleApp Release&lt;/code&gt; for the production environment.&lt;/p&gt;&lt;p&gt;Each scheme is linked to a target and lets us assign which build configuration it should use when running or testing.&lt;/p&gt;&lt;h3&gt;What is a build configuration?&lt;/h3&gt;&lt;p&gt;A build configuration tells Xcode how to build a target. Each one has its own set of build settings.&lt;/p&gt;&lt;p&gt;By default, Xcode gives us two:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Debug: fast builds, debug info included&lt;/li&gt;&lt;li&gt;Release: optimized builds, no debug info&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We can add more, like Staging, to test production-like environments.&lt;/p&gt;&lt;h3&gt;What&amp;#x27;s next?&lt;/h3&gt;&lt;p&gt;By combining targets with schemes and build configurations, we can fully control our build pipeline for each environment.&lt;/p&gt;&lt;p&gt;➡️ Continue reading: &lt;a href=&quot;/xcode-build-environments/&quot;&gt;Creating and managing multiple app environments in Xcode&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>xcode</category><category>ios</category><category>swift</category></item><item><title><![CDATA[Bridging interfaces with the Adapter pattern in Swift]]></title><description><![CDATA[Learn how to use the Adapter pattern to integrate third-party APIs or legacy code.]]></description><link>https://tanaschita.com/swift-design-patterns-adapter</link><guid isPermaLink="false">https://tanaschita.com/swift-design-patterns-adapter</guid><pubDate>Sun, 27 Apr 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes, we adopt libraries or frameworks that don&amp;#x27;t match our app&amp;#x27;s internal abstractions. Or, as our architecture evolves, existing components no longer fit with newer code cleanly. Instead of rewriting working code, we can adapt it with the Adapter pattern.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do it with a practical example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s say our app expects a &lt;code&gt;SearchService&lt;/code&gt; protocol that defines how search functionality should work across different parts of the app:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol SearchService {
    func search(query: String) async throws -&amp;gt; [SearchResult]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But we&amp;#x27;re integrating a third-party SDK with the following interface:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class ThirdPartySearch {
    func runSearch(term: String, completion: @escaping ([Any]) -&amp;gt; Void) {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The SDK&amp;#x27;s API uses different method names, a completion handler instead of &lt;code&gt;async/await&lt;/code&gt;, and returns untyped results.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s how we can bridge that gap:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class ThirdPartySearchAdapter: SearchService {
    private let thirdPartySearch = ThirdPartySearch()

    func search(query: String) async throws -&amp;gt; [SearchResult] {
        try await withCheckedThrowingContinuation { continuation in
            thirdPartySearch.runSearch(term: query) { rawResults in
                let results = rawResults.compactMap { $0 as? SearchResult }
                continuation.resume(returning: results)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can use &lt;code&gt;ThirdPartySearchAdapter&lt;/code&gt; anywhere a &lt;code&gt;SearchService&lt;/code&gt; is expected.&lt;/p&gt;&lt;p&gt;The Adapter pattern helps us preserve encapsulation and maintain clean separation between domains, making our code more resilient to change.&lt;/p&gt;</content:encoded><category>design patterns</category><category>architecture</category><category>swift</category></item><item><title><![CDATA[How to persist navigation state in SwiftUI]]></title><description><![CDATA[Learn how to save and restore NavigationPath to maintain user context across app launches.]]></description><link>https://tanaschita.com/swiftui-navigation-persist-state</link><guid isPermaLink="false">https://tanaschita.com/swiftui-navigation-persist-state</guid><pubDate>Sun, 20 Apr 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building multi-screen apps in SwiftUI, preserving where a user left off can improve the user experience. One of the key advantages of SwiftUI&amp;#x27;s &lt;code&gt;NavigationPath&lt;/code&gt; is that it&amp;#x27;s serializable. As long as all data types in the path conform to &lt;code&gt;Codable&lt;/code&gt;, we can save and restore the navigation state.&lt;/p&gt;&lt;p&gt;This allows us to persist the navigation stack across app launches or even sync state between devices.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Making our routes codable&lt;/h3&gt;&lt;p&gt;To persist a &lt;code&gt;NavigationPath&lt;/code&gt;, every element that goes into it must conform to both &lt;code&gt;Hashable&lt;/code&gt; and &lt;code&gt;Codable&lt;/code&gt;. If, for example, we used a &lt;a target=&quot;_blank&quot; href=&quot;/swiftui-navigationpath/&quot;&gt;router based approach&lt;/a&gt; to work with &lt;code&gt;NavigationPath&lt;/code&gt;, it can look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Route: Hashable, Codable {
    case search
    case detail(id: String)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Encoding and storing navigation state&lt;/h3&gt;&lt;p&gt;Once the route type is &lt;code&gt;Codable&lt;/code&gt;, we can encode the navigation path and store it using a persistence mechanism such as &lt;code&gt;UserDefaults&lt;/code&gt; or the file system.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// Encode navigation path
if let encodedPath = try? JSONEncoder().encode(navigationPath) {
    UserDefaults.standard.set(encodedPath, forKey: &amp;quot;savedNavigation&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For larger or sensitive data, we could use the file system or Keychain instead.&lt;/p&gt;&lt;h3&gt;Restoring the navigation stack&lt;/h3&gt;&lt;p&gt;To restore navigation, we can retrieve the encoded path and decode it back into a &lt;code&gt;NavigationPath&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let data = UserDefaults.standard.data(forKey: &amp;quot;savedNavigation&amp;quot;),
   let decodedPath = try? JSONDecoder().decode(NavigationPath.self, from: data) {
    navigationPath = decodedPath
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can perform this restoration early in the app lifecycle, for example when initializing a router.&lt;/p&gt;&lt;h3&gt;Limitations to keep in mind&lt;/h3&gt;&lt;p&gt;While persisting navigation state can be powerful, there are a few important things to consider. First, only types that conform to &lt;code&gt;Codable&lt;/code&gt; can be stored in a &lt;code&gt;NavigationPath&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Second, our route definitions must remain stable over time. If we modify the structure of a route, for example by adding or removing enum cases, previously saved navigation data may no longer decode properly.&lt;/p&gt;&lt;p&gt;Finally, we should be mindful of what data we include in our routes. Associated values should be lightweight and safe to persist, especially if using mechanisms like &lt;code&gt;UserDefaults&lt;/code&gt; for persistence.&lt;/p&gt;</content:encoded><category>navigation</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Using NavigationPath with TabView in SwiftUI]]></title><description><![CDATA[Learn how to integrate NavigationPath with TabView in SwiftUI to manage independent navigation flows.]]></description><link>https://tanaschita.com/swiftui-navigation-path-with-tabview</link><guid isPermaLink="false">https://tanaschita.com/swiftui-navigation-path-with-tabview</guid><pubDate>Sun, 13 Apr 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building complex iOS applications, we often need to combine tab-based navigation with stack-based navigation. In SwiftUI, this means integrating &lt;code&gt;TabView&lt;/code&gt; with &lt;code&gt;NavigationStack&lt;/code&gt;. A common challenge in this scenario is managing navigation state properly across multiple tabs while maintaining independent navigation flows.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at one possible solution. We&amp;#x27;ll create a central app router to coordinate navigation and give each tab its own router and &lt;code&gt;NavigationPath&lt;/code&gt;. This way, we will gain full control of the navigation, allowing for clean separation of concerns, deep linking support, and a more modular design.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Designing the app router&lt;/h3&gt;&lt;p&gt;We&amp;#x27;ll start by creating an app-wide router, which allows us to switch between tabs and go to any place in our iOS application.&lt;/p&gt;&lt;p&gt;Here is an example implementation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Observable
class AppRouter {
    var searchRouter = SearchRouter()
    var settingsRouter = SettingsRouter()
    var selectedTab = Tab.search.rawValue

    func navigateTo(tab: Tab) {
        selectedTab = tab.rawValue
    }
}

enum Tab: Int {
    case search = 0
    case settings = 1
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;AppRouter&lt;/code&gt; is responsible for holding the currently selected tab and routers for each tab&amp;#x27;s navigation flow.&lt;/p&gt;&lt;h3&gt;Creating per-tab routers&lt;/h3&gt;&lt;p&gt;Since each tab needs it&amp;#x27;s own navigation stack, we create a separate router for each tab. A tab router owns a &lt;code&gt;NavigationPath&lt;/code&gt; and defines the types of views (routes) that can be pushed onto the navigation stack.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Observable
class SearchRouter {
    var navigationPath = NavigationPath()

    enum Route: Hashable {
        case detail(itemId: String)
    }

    func navigateTo(route: Route) {
        navigationPath.append(route)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we defined the router for a search flow, where a user can navigate to a detail view after searching for an item.&lt;/p&gt;&lt;h3&gt;Putting it together with TabView and NavigationStack&lt;/h3&gt;&lt;p&gt;With the &lt;code&gt;AppRouter&lt;/code&gt; and individual tab routers in place, we can wire everything together inside our root view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct RootTabView: View {
    @State private var appRouter = AppRouter()

    var body: some View {
        TabView(selection: $appRouter.selectedTab) {
            SearchRootView(router: appRouter.searchRouter)
                .tabItem { Label(&amp;quot;Search&amp;quot;, systemImage: &amp;quot;magnifyingglass&amp;quot;) }
                .tag(Tab.search.rawValue)

            SettingsRootView(router: appRouter.settingsRouter)
                .tabItem { Label(&amp;quot;Settings&amp;quot;, systemImage: &amp;quot;gearshape&amp;quot;) }
                .tag(Tab.settings.rawValue)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each tab&amp;#x27;s root view receives its associated router.&lt;/p&gt;&lt;h3&gt;Navigating within a tab&lt;/h3&gt;&lt;p&gt;Here&amp;#x27;s what the root view for the search tab could look like:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SearchRootView: View {
    @Bindable var router: SearchRouter

    var body: some View {
        NavigationStack(path: $router.navigationPath) {
            SearchView()
                .navigationDestination(for: SearchRouter.Route.self) { route in
                    switch route {
                    case .detail(let itemId):
                        DetailView(itemId: itemId)
                    }
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By binding &lt;code&gt;NavigationStack&lt;/code&gt; to the &lt;code&gt;router.navigationPath&lt;/code&gt;, we give the search tab its own independent navigation history.&lt;/p&gt;&lt;h3&gt;Navigating to specific views&lt;/h3&gt;&lt;p&gt;With the setup shown above, we have now full control of the navigation in our app. For example, we can use it to handle universal links as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.onOpenURL { url in
    if let universalLink = UniversalLink(from: url) {
        switch universalLink {
        case .detail(let itemId):
            appRouter.navigateTo(tab: Tab.search)
            appRouter.searchRouter.navigateTo(route: .detail(itemId: itemId))
        // Handle other cases.
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This logic switches to the correct tab and then updates that tab&amp;#x27;s navigation path to push the appropriate view.&lt;/p&gt;&lt;h3&gt;Wrapping up&lt;/h3&gt;&lt;p&gt;Using &lt;code&gt;NavigationPath&lt;/code&gt; with &lt;code&gt;TabView&lt;/code&gt; in SwiftUI is a powerful way to manage complex navigation scenarios. By assigning each tab its own &lt;code&gt;NavigationPath&lt;/code&gt;, we avoid the pitfalls of shared navigation state and gain the ability to support deep linking, preserve user context, and scale navigation logic cleanly.&lt;/p&gt;</content:encoded><category>navigation</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Testing remote iOS push notifications in a simulator with simctl]]></title><description><![CDATA[Learn how to use the command-line tool simctl to simulate remote notifications in iOS.]]></description><link>https://tanaschita.com/testing-remote-push-notifications-in-ios-simulator</link><guid isPermaLink="false">https://tanaschita.com/testing-remote-push-notifications-in-ios-simulator</guid><pubDate>Sat, 05 Apr 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Testing remote push notifications in iOS has traditionally been challenging. Developers often lack access to the production server responsible for sending notifications, and since remote notifications are not natively supported in the iOS simulator, a physical device is usually required to receive a valid push notification token.&lt;/p&gt;&lt;p&gt;However, Apple provides a command-line tool called &lt;code&gt;simctl&lt;/code&gt; that allows developers to simulate push notifications directly in the simulator, making testing much easier.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Creating and storing an .apn file&lt;/h3&gt;&lt;p&gt;The first step to simulate a push notification in a simulator is creating and storing an &lt;code&gt;.apn&lt;/code&gt; file. The file contains the JSON notification payload that the app would normally receive from a server.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s an example &lt;code&gt;test.apn&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;aps&amp;quot;: {
    &amp;quot;alert&amp;quot;: &amp;quot;Hello world&amp;quot;,
    &amp;quot;sound&amp;quot;: &amp;quot;default&amp;quot;,
  }
  &amp;quot;categoryId&amp;quot;: &amp;quot;1234&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above example includes an alert message and a sound notification.&lt;/p&gt;&lt;h3&gt;Simulating a push notification with simctl&lt;/h3&gt;&lt;p&gt;&lt;code&gt;simctl&lt;/code&gt; is a command-line tool that allows control of the iOS simulator. With Xcode installed, &lt;code&gt;simctl&lt;/code&gt; is accessible through the &lt;code&gt;xcrun&lt;/code&gt; command.&lt;/p&gt;&lt;p&gt;The easiest way to send a simulated notification is by following these steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Run the app that supports and handles push notifications in a simulator.&lt;/li&gt;&lt;li&gt;Open the terminal.&lt;/li&gt;&lt;li&gt;Navigate to the directory containing the &lt;code&gt;test.apn&lt;/code&gt; file.&lt;/li&gt;&lt;li&gt;Run the following command:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcrun simctl push booted &amp;lt;bundle-identifier&amp;gt; test.apn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replace &lt;code&gt;&amp;lt;bundle-identifier&amp;gt;&lt;/code&gt; with your app&amp;#x27;s bundle identifier (e.g., com.example.MyApp).&lt;/p&gt;&lt;p&gt;After executing the command, you should see the push notification appear in the simulator, just as it would on a physical device.&lt;/p&gt;&lt;h3&gt;Advanced usage&lt;/h3&gt;&lt;p&gt;We can target specific simulators rather than just the booted one. First, list available simulators:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcrun simctl list devices
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, we can use the device UUID instead of &amp;quot;booted&amp;quot;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcrun simctl push &amp;lt;device-uuid&amp;gt; &amp;lt;bundle-identifier&amp;gt; test.apn
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Testing different notification scenarios&lt;/h3&gt;&lt;p&gt;Once the setup is working, we can test various scenarios by modifying the &lt;code&gt;test.apn&lt;/code&gt; file, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Deep linking: By adding a &lt;code&gt;url&lt;/code&gt; key to test how the iOS app handles notification-based deep links.&lt;/li&gt;&lt;li&gt;Action buttons: By adding an &lt;code&gt;action&lt;/code&gt; key to test interactive notifications.&lt;/li&gt;&lt;li&gt;Custom data: By including additional payload data to test how the iOS app processes it.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For a full list of notification payload options, check out Apple’s &lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2943363&quot;&gt;official payload key reference&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>notifications</category><category>testing</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Building a dependency injection framework]]></title><description><![CDATA[Learn how to create a lightweight dependency injection container in Swift]]></description><link>https://tanaschita.com/dependency-injection-building-lightweight-container</link><guid isPermaLink="false">https://tanaschita.com/dependency-injection-building-lightweight-container</guid><pubDate>Mon, 31 Mar 2025 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dependency injection (DI) is a powerful design pattern that helps us create more modular, testable, and maintainable code. There are many excellent third-party DI frameworks available for Swift. While these tools save us from a lot of manual work, they also abstract away many of the challenges involved in dependency injection.&lt;/p&gt;&lt;p&gt;Understanding how these frameworks work under the hood can give us deeper insight into the design patterns and principles behind them. In this article, we will build our own lightweight DI container to gain a deeper understanding on how DI containers work by solving common dependency management problems such registering and resolving dependencies dynamically, handling object lifecycle, creating a property wrapper for easy-to-use resolution and more.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Designing our DI container&lt;/h3&gt;&lt;p&gt;Our minimal DI container should be able to:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Register dependencies&lt;/strong&gt; so that they can be resolved later.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Resolve dependencies dynamically&lt;/strong&gt;, ensuring that instances are created when needed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Manage lifecycles&lt;/strong&gt;, whether they should be created anew each time or persist as singletons.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Inject dependencies easily&lt;/strong&gt; by using a property wrapper for automatic resolution.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Our framework will have two main components:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A &lt;code&gt;DIContainer&lt;/code&gt; for registering and resolving dependencies&lt;/li&gt;&lt;li&gt;A &lt;code&gt;@Injected&lt;/code&gt; property wrapper for easy usage&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To start, we&amp;#x27;ll create a simple &lt;code&gt;DIContainer&lt;/code&gt; class that allows us to register and retrieve dependencies. The basic idea is to store a mapping between a type and a factory function that produces an instance of that type. When a dependency is requested, the container will invoke the corresponding factory function to return an instance.&lt;/p&gt;&lt;h3&gt;Implementing the DI container&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s begin with a simple version of our DI container:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;public class DIContainer {
    private var factories: [String: Any] = [:]

    func register&amp;lt;T&amp;gt;(_ type: T.Type, factory: @escaping () -&amp;gt; T) {
        let key = String(describing: type)
        factories[key] = factory
    }

    func resolve&amp;lt;T&amp;gt;(_ type: T.Type) -&amp;gt; T? {
        let key = String(describing: type)
        guard let factory = factories[key] as? () -&amp;gt; T else { return nil}
        let instance = factory()
        return instance
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This implementation allows us to register dependencies using a factory closure and resolve them by type. For instance, we can register a &lt;code&gt;NetworkService&lt;/code&gt; like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let container = DIContainer()
container.register(NetworkServiceProtocol.self, factory: { NetworkService() })

let networkService = container.resolve(NetworkServiceProtocol.self)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each time we call &lt;code&gt;resolve()&lt;/code&gt;, a new instance is created. However, for dependencies that should persist throughout the application&amp;#x27;s lifecycle, such as a shared network service, we need to introduce singleton support.&lt;/p&gt;&lt;h3&gt;Adding dependency lifetimes&lt;/h3&gt;&lt;p&gt;Different dependencies have different lifecycle requirements. Some should be instantiated each time they are requested, while others should persist across multiple resolutions. To handle this, we can introduce a &lt;code&gt;DependencyLifetime&lt;/code&gt; enum to distinguish between &lt;code&gt;transient&lt;/code&gt; and &lt;code&gt;singleton&lt;/code&gt; lifetimes.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;public enum DependencyLifetime {
    case singleton
    case transient
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we modify our &lt;code&gt;DIContainer&lt;/code&gt; to store singleton instances separately and respect the chosen lifetime when resolving dependencies:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;public class DIContainer {
    static let shared = DIContainer()

    private var factories: [String: Any] = [:]
    private var singletons: [String: Any] = [:]
    private var lifetimes: [String: DependencyLifetime] = [:]

    private init() {}

    func register&amp;lt;T&amp;gt;(_ type: T.Type, factory: @escaping () -&amp;gt; T, lifetime: DependencyLifetime) {
        let key = String(describing: type)
        factories[key] = factory
        lifetimes[key] = lifetime
    }

    func resolve&amp;lt;T&amp;gt;(_ type: T.Type) -&amp;gt; T? {
        let key = String(describing: type)
        guard let lifetime = lifetimes[key] else { return nil }

        switch lifetime {
        case .transient:
            guard let factory = factories[key] as? () -&amp;gt; T else { return nil }
            return factory()
        case .singleton:
            if let instance = singletons[key] as? T {
                return instance
            }
            guard let factory = factories[key] as? () -&amp;gt; T else { return nil }
            let instance = factory()
            singletons[key] = instance
            return instance
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can create singleton dependencies when needed:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;container.register(NetworkServiceProtocol.self, factory: { NetworkService() }, lifetime: .singleton)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Using a property wrapper for dependency injection&lt;/h3&gt;&lt;p&gt;To simplify dependency resolution, we can introduce a property wrapper that automatically resolves dependencies when accessed:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@propertyWrapper
public struct Injected&amp;lt;T&amp;gt; {
    private var dependency: T

    public init() {
        guard let dependency = DIContainer.shared.resolve(T.self) else {
            fatalError(&amp;quot;No dependency registered for \(T.self)&amp;quot;)
        }
        self.dependency = dependency
    }

    public var wrappedValue: T {
        return dependency
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This property wrapper provides a declarative way to inject dependencies and automatically resolves dependencies from the container. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class UserService {
    @Injected var networkService: NetworkServiceProtocol
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Putting it all together - a usage example&lt;/h3&gt;&lt;p&gt;To demonstrate our DI framework in action, let’s set up an application where we define dependencies, register them in the container, and inject them where needed.&lt;/p&gt;&lt;p&gt;First, we define the protocols and implementations:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol NetworkServiceProtocol {
    func fetchData() -&amp;gt; String
}

class NetworkService: NetworkServiceProtocol {
    func fetchData() -&amp;gt; String {
        return &amp;quot;Real network data&amp;quot;
    }
}

class MockNetworkService: NetworkServiceProtocol {
    func fetchData() -&amp;gt; String {
        return &amp;quot;Mock network data&amp;quot;
    }
}

class UserService {
    @Injected var networkService: NetworkServiceProtocol

    func loadProfile() {
        let data = networkService.fetchData()
        print(data)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we configure our container at application startup:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct ExampleApp: App {

    init() {
        DIContainer.setup()
    }

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

extension DIContainer {
    static func setup() {
        let container = DIContainer.shared
        container.register(NetworkServiceProtocol.self, factory: { NetworkService() }, lifetime: .singleton)
        container.register(UserService.self, factory: { UserService() }, lifetime: .singleton)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can use dependency injection, for example, in a view model:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Observable
class UserViewModel {
    @ObservationIgnored @Injected var userService: UserService
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working with the &lt;code&gt;@Observable&lt;/code&gt; framework, it&amp;#x27;s important to mark injected dependencies with &lt;code&gt;@ObservationIgnored&lt;/code&gt;. This prevents SwiftUI from tracking changes to the dependency, which would lead to unnecessary re-renders. Since dependencies like &lt;code&gt;userService&lt;/code&gt; typically don&amp;#x27;t change in a way that affects the view, excluding them from observation ensures that only the state relevant to the UI is observed, improving performance and reducing unnecessary updates.&lt;/p&gt;&lt;h3&gt;Finaly thoughts&lt;/h3&gt;&lt;p&gt;In this article, we looked at the code idea of a DI container to better understand some concepts behind DI containers. While this implementation covers the basics, there are several areas for improvement, such as better key handling, thread safety, error handling, additional lifecycle options and more.&lt;/p&gt;</content:encoded><category>dependency management</category><category>property wrappers</category><category>swift</category><category>swiftui</category></item><item><title><![CDATA[Public-key cryptography with CryptoKit for iOS]]></title><description><![CDATA[Learn how to use CryptoKit to increase security in iOS applications.]]></description><link>https://tanaschita.com/cryptokit-public-key-cryptography</link><guid isPermaLink="false">https://tanaschita.com/cryptokit-public-key-cryptography</guid><pubDate>Sun, 23 Mar 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Public-key cryptography, also known as asymmetric cryptography, is a cornerstone of modern security. It ensures secure communication, authentication, and data integrity across IT systems and networks.&lt;/p&gt;&lt;p&gt;For iOS developers, Apple provides built-in security with TLS encryption and digital signing when communicating over HTTPS. However, when dealing with sensitive user data, authentication, or offline encryption, additional layers of security might be necessary.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore how public-key cryptography works and how CryptoKit simplifies its implementation in iOS applications.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Public-key cryptography basics&lt;/h3&gt;&lt;p&gt;Public-key cryptography is based on a pair of linked keys:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A &lt;strong&gt;public key&lt;/strong&gt; (which can be freely shared)&lt;/li&gt;&lt;li&gt;A &lt;strong&gt;private key&lt;/strong&gt; (which must remain secret)&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Anyone can use the public key to encrypt messages, but only the holder of the private key can decrypt them. This makes it ideal for secure messaging, authentication, and digital signatures.&lt;/p&gt;&lt;p&gt;A crucial aspect of public-key cryptography is that deriving the private key from the public key is computationally infeasible. This ensures secure communication even if an attacker intercepts the public key.&lt;/p&gt;&lt;h3&gt;Public-key cryptography use cases&lt;/h3&gt;&lt;p&gt;Common public-key cryptography use cases are encryption and digital signatures.&lt;/p&gt;&lt;h4&gt;Use case: Digital signatures&lt;/h4&gt;&lt;p&gt;A digital signature proves that a message was sent by a specific party and has not been altered. This is essential for secure messaging, online transactions, and authentication systems.&lt;/p&gt;&lt;p&gt;In more detail, it works as follows:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The sender &lt;a href=&quot;/swift-hash-functions/&quot;&gt;hashes&lt;/a&gt; the message to create a message digest (a fixed-length fingerprint of the data).&lt;/li&gt;&lt;li&gt;The digest is then encrypted with the sender&amp;#x27;s private key, creating a digital signature.&lt;/li&gt;&lt;li&gt;The recipient decrypts the signature with the sender&amp;#x27;s public key to retrieve the digest.&lt;/li&gt;&lt;li&gt;The recipient hashes the original message and compares the digests.&lt;/li&gt;&lt;li&gt;If they match, the message is authentic and untampered.&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;Use case: Encryption&lt;/h4&gt;&lt;p&gt;Although public keys can be used to encrypt messages, they are typically used for secure key exchange in combination with &lt;a href=&quot;/cryptokit-symmetric-key-cryptography/&quot;&gt;symmetric encryption&lt;/a&gt; which is faster and more efficient for large data.&lt;/p&gt;&lt;p&gt;For example, when a user connects to a secure server, public-key cryptography is used to securely exchange a symmetric key that is then used for encrypting communication.&lt;/p&gt;&lt;h3&gt;Public-key algorithms supported by CryptoKit&lt;/h3&gt;&lt;p&gt;CryptoKit supports Elliptic Curve Cryptography (ECC), which is more efficient than traditional RSA encryption.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;P256/P384/P521&lt;/strong&gt; (NIST curves) – Widely used, standardized, and compliant with government security standards.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Curve25519&lt;/strong&gt; – More efficient and resistant to certain attacks (e.g., side-channel attacks). It&amp;#x27;s often recommended for modern applications due to its performance and simplicity.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For most applications, Curve25519 is the best choice unless compliance with NIST standards is required.&lt;/p&gt;&lt;h3&gt;Using CryptoKit&lt;/h3&gt;&lt;p&gt;Now, let&amp;#x27;s see how to implement public-key cryptography in Swift using CryptoKit.&lt;/p&gt;&lt;h4&gt;1. Creating a key pair with CryptoKit&lt;/h4&gt;&lt;p&gt;Creating a private-public key pair with CryptoKit is as simple as:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let privateKey = Curve25519.Signing.PrivateKey()
let publicKey = privateKey.publicKey
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To send the public key, we can convert it to &lt;code&gt;Data&lt;/code&gt; by using its &lt;code&gt;rawRepresentation&lt;/code&gt; property:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let publicKeyData = publicKey.rawRepresentation
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we want to use &lt;code&gt;P256&lt;/code&gt; instead of &lt;code&gt;Curve25519&lt;/code&gt;, we simply swap the algorithm:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let privateKey = P256.Signing.PrivateKey()
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. Signing with CryptoKit&lt;/h4&gt;&lt;p&gt;We can create a digital signature using the private key:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let signature = try privateKey.signature(for: messageDigest)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On &lt;code&gt;signature&lt;/code&gt;, we can again use its &lt;code&gt;rawRepresentation&lt;/code&gt; property to get a &lt;code&gt;Data&lt;/code&gt; type for server communication.&lt;/p&gt;&lt;p&gt;Verifying a received signature is similarly simple:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let isValid = publicKey.isValidSignature(signature, for: messageDigest)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;With just a few lines of Swift code, CryptoKit allows us to generate key pairs, create digital signatures, and verify them, all while benefiting from Apple&amp;#x27;s optimized and secure cryptographic implementations.&lt;/p&gt;</content:encoded><category>cryptokit</category><category>security</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding existentials and primary associated types in Swift]]></title><description><![CDATA[How existentials and primary associated types enhance flexibility in protocols.]]></description><link>https://tanaschita.com/swift-existentials</link><guid isPermaLink="false">https://tanaschita.com/swift-existentials</guid><pubDate>Sun, 16 Mar 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Existentials are useful when working with protocols that have associated types. In Swift, protocols with associated types cannot be used as standalone types because their concrete type is unknown at compile time. To work around this, Swift allows us to use existentials (&lt;code&gt;any Protocol&lt;/code&gt;) to enable type erasure and store different conforming types in a single collection.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s explore how they work in practice.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s consider an example where we&amp;#x27;ll create a system to manage different geometric shapes such as circles, rectangles or triangles and calculate their areas. Each shape conforms to a common protocol &lt;code&gt;Shape&lt;/code&gt; which defines a method for calculating the area:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol Shape {
    associatedtype Parameters
    func area(with parameters: Parameters) -&amp;gt; Double
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, the &lt;code&gt;Parameters&lt;/code&gt; associated type will be different for each shape. For example, the parameters for a circle might be the radius, while the parameters for a rectangle could be its width and height.&lt;/p&gt;&lt;h3&gt;Implementing concrete shapes&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s define two shape implementations: &lt;code&gt;Circle&lt;/code&gt; and &lt;code&gt;Rectangle&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Circle: Shape {
    struct CircleParameters {
        let radius: Double
    }

    func area(with parameters: CircleParameters) -&amp;gt; Double {
        return Double.pi * parameters.radius * parameters.radius
    }
}

struct Rectangle: Shape {
    struct RectangleParameters {
        let width: Double
        let height: Double
    }

    func area(with parameters: RectangleParameters) -&amp;gt; Double {
        return parameters.width * parameters.height
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Following the same structure, we could define more shapes, such as &lt;code&gt;Triangle&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Storing shapes in a collection&lt;/h3&gt;&lt;p&gt;If we try to store different shapes in an array using the Shape protocol directly, we run into a compiler error:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// Compiler error: Protocol &amp;#x27;Shape&amp;#x27; can only be used as a generic constraint because it has Self or associated type requirements.
var shapes: [Shape] = [Circle(), Rectangle(), Triangle()]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Swift does not allow storing values of a protocol with associated types in a collection unless their concrete types are known at compile time. This is where where existentials come into play:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var shapes: [any Shape] = [Circle(), Rectangle(), Triangle()]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, Swift allows different implementations to coexist in the same array, but with the trade-off of losing static type information. This means we don&amp;#x27;t know the specific type of each shape at compile time. To work around this, we must cast the shapes back to their concrete types before calling their methods.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let circleParameters = Circle.CircleParameters(radius: 10)
let rectangleParameters = Rectangle.RectangleParameters(width: 5, height: 8)

for shape in shapes {
    if let circle = shape as? Circle {
        print(&amp;quot;Circle area: \(circle.area(with: circleParameters))&amp;quot;)
    } else if let rectangle = shape as? Rectangle {
        print(&amp;quot;Rectangle area: \(rectangle.area(with: rectangleParameters))&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this works, it introduces runtime type checking and casting, which can be error-prone.&lt;/p&gt;&lt;h3&gt;Using primary associated types&lt;/h3&gt;&lt;p&gt;To reduce the need for type casting, Swift provides &lt;strong&gt;primary associated types&lt;/strong&gt;, allowing us to specify associated types explicitly when working with existentials:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol Shape&amp;lt;Parameters&amp;gt; {
    associatedtype Parameters
    func area(with parameters: Parameters) -&amp;gt; Double
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can use &lt;code&gt;Shape&amp;lt;CircleParameters&amp;gt;&lt;/code&gt; to define a concrete type when working with the protocol. However, for heterogeneous collections, we still need to use &lt;code&gt;any&lt;/code&gt; without a concrete associated type.&lt;/p&gt;&lt;p&gt;While primary associated types help restore lost type information, they do not introduce additional trade-offs when used with &lt;code&gt;any&lt;/code&gt;. Instead, they extend the flexibility of existentials, allowing us to specify associated types when needed while still leveraging &lt;code&gt;any&lt;/code&gt; for heterogeneous collections.&lt;/p&gt;</content:encoded><category>generics and protocols</category><category>swift</category></item><item><title><![CDATA[Understanding structural identity in SwiftUI]]></title><description><![CDATA[Learn how SwiftUI optimizes UI updates.]]></description><link>https://tanaschita.com/swiftui-structural-identity</link><guid isPermaLink="false">https://tanaschita.com/swiftui-structural-identity</guid><pubDate>Sun, 09 Mar 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Unlike traditional UI frameworks where we manually track and update UI elements, SwiftUI automatically refreshes views when state changes. However, redrawing everything would be inefficient. This is where structural identity comes in - it allows SwiftUI to determine which views actually need to be redrawn, optimizing performance.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in and look at how this works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is structural identity?&lt;/h3&gt;&lt;p&gt;When a state change occurs in SwiftUI, the framework reconstructs the entire view hierarchy. This might sound inefficient at first, but it&amp;#x27;s actually remarkably optimized, because SwiftUI views are lightweight value types (structs) and most importantly, because SwiftUI uses structural identity to avoid unneeded redrawings.&lt;/p&gt;&lt;p&gt;Structural identity is SwiftUI&amp;#x27;s way of recognizing whether a view before and after a state change is fundamentally the same view. When SwiftUI identifies views as structurally identical, it preserves their state and redraws only the parts that have changed.&lt;/p&gt;&lt;p&gt;A view&amp;#x27;s structural identity is determined by:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Its type&lt;/li&gt;&lt;li&gt;Its position in the view hierarchy&lt;/li&gt;&lt;li&gt;The identity of its ancestors&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Update vs. redraw in SwiftUI&lt;/h3&gt;&lt;p&gt;While the terms update and redraw may sound interchangeably, they refer to different processes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Update&lt;/strong&gt;: SwiftUI reinitializes the view evaluating the &lt;code&gt;body&lt;/code&gt; property, while preserving state. This happens every time a state change occurs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Redraw&lt;/strong&gt;: This is when SwiftUI sends rendering instructions to the GPU, updating the pixels on the screen. Just because a view is reinitialized doesn&amp;#x27;t mean it will be redrawn.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Structural identity in action&lt;/h2&gt;&lt;p&gt;Consider this example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ExampleView: View {
    @State private var isOn = true

    var body: some View {
        VStack {
            Text(&amp;quot;Is on: \(isOn ? &amp;quot;yes&amp;quot;: &amp;quot;no&amp;quot;)&amp;quot;)
            Button(&amp;quot;Switch&amp;quot;) {
                isOn.toggle()
            }
            if (isOn) {
                BottomViewOn()
            } else {
                BottomViewOff()
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When a user taps the &amp;quot;Switch&amp;quot; button:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The &lt;code&gt;isOn&lt;/code&gt; state variable changes.&lt;/li&gt;&lt;li&gt;SwiftUI rebuilds the view hierarchy. That means &lt;code&gt;ExampleView&lt;/code&gt; is reinitialized, and SwiftUI preserves the state &lt;code&gt;isOn&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Using structural identity, SwiftUI recognizes that &lt;code&gt;Text&lt;/code&gt; and &lt;code&gt;Button&lt;/code&gt; are fundamentally the same views as before.&lt;/li&gt;&lt;li&gt;Since the &lt;code&gt;Text&lt;/code&gt; view&amp;#x27;s content has changed, it gets redrawn.&lt;/li&gt;&lt;li&gt;The &lt;code&gt;Button&lt;/code&gt; view remains untouched because its content and structure haven&amp;#x27;t changed.&lt;/li&gt;&lt;li&gt;What happens with &lt;code&gt;BottomViewOn&lt;/code&gt; and &lt;code&gt;BottomViewOff&lt;/code&gt;? When &lt;code&gt;isOn&lt;/code&gt; changes, one view is removed from the hierarchy, and the other one is newly created. And any internal state within &lt;code&gt;BottomViewOn&lt;/code&gt; or &lt;code&gt;BottomViewOff&lt;/code&gt; is lost when switching between them.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;How to persist state across both views?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;If we want to keep state in &lt;code&gt;BottomViewOn&lt;/code&gt; and &lt;code&gt;BottomViewOff&lt;/code&gt;, we could:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Extract their state to the parent view e.i. manage it externally or&lt;/li&gt;&lt;li&gt;Use structural identity by making the following changes:&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ZStack {
    BottomViewOn()
        .opacity(isOn ? 1 : 0)
        .accessibilityHidden(!isOn)
    BottomViewOff()
        .opacity(isOn ? 0 : 1)
        .accessibilityHidden(isOn)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead using an if-else condition, we now keep both views in the hierarchy preserving structural identity.&lt;/p&gt;&lt;h3&gt;Structural identity in lists&lt;/h3&gt;&lt;p&gt;In SwiftUI, structural identity also plays an important role in lists. When using a &lt;code&gt;List&lt;/code&gt; view, SwiftUI requires each item to have a unique identifier, either by conforming to &lt;code&gt;Identifiable&lt;/code&gt; or by specifying an &lt;code&gt;id&lt;/code&gt; key path. This allows SwiftUI to track individual items even when their order changes, preventing unnecessary redraws. Using unique IDs such as database IDs is a good practice because relying on indices or non-unique properties can lead to incorrect updates, glitches, or unexpected UI behavior when modifying the list dynamically.&lt;/p&gt;&lt;h3&gt;SwiftUI as a black box&lt;/h3&gt;&lt;p&gt;SwiftUI hides much of its inner workings, making it feel like a black box. SwiftUI decides what to update and redraw behind the scenes, so we can only experiment to understand its behavior. Many hope SwiftUI will be open-sourced someday, giving us a clearer look inside.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding the Bindable property wrapper in SwiftUI]]></title><description><![CDATA[Learn how and when to use @Bindable in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-observation-bindable</link><guid isPermaLink="false">https://tanaschita.com/swiftui-observation-bindable</guid><pubDate>Sun, 02 Mar 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with Apple&amp;#x27;s Observation framework, understanding how different property wrappers manage state is essential for building apps with SwiftUI.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll focus on &lt;code&gt;@Bindable&lt;/code&gt;, a property wrapper that allows child views to create bindings to properties inside an &lt;code&gt;@Observable&lt;/code&gt; model.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s start with an example where two views share the same view model:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Observation

@Observable
class UserViewModel {
    var name: String?
    var age: Int?
}

struct ParentView: View {
    @State private var userViewModel = UserViewModel()

    var body: some View {
        VStack {
            Text(userViewModel.name ?? &amp;quot;&amp;quot;)
            ChildView(userViewModel: $userViewModel)
        }
    }
}

struct ChildView: View {
    let userViewModel: UserViewModel

    var body: some View {
        VStack {
            Text(userViewModel.name ?? &amp;quot;&amp;quot;)
            Button(action: {
                userViewModel.age += 1
            }, label: {
                Text(&amp;quot;Increment age&amp;quot;)
            })
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Why no property wrapper is needed here&lt;/h3&gt;&lt;p&gt;In the example above, &lt;code&gt;ChildView&lt;/code&gt; does not use any property wrapper to reference &lt;code&gt;userViewModel&lt;/code&gt;. Since &lt;code&gt;UserViewModel&lt;/code&gt; is marked as &lt;code&gt;@Observable&lt;/code&gt;, SwiftUI automatically tracks changes and updates dependent views when properties change. Thus, there&amp;#x27;s no need to use &lt;code&gt;@Bindable&lt;/code&gt; in this scenario.&lt;/p&gt;&lt;h3&gt;When do we need @Bindable?&lt;/h3&gt;&lt;p&gt;While direct property access works in many cases, certain UI components, such as &lt;code&gt;TextField&lt;/code&gt;, require bindings to modify a value. Consider the following scenario:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ChildView: View {
    let userViewModel: UserViewModel

    var body: some View {
        VStack {
            // Compiler error: Cannot find &amp;#x27;$userViewModel&amp;#x27; in scope
            TextField(&amp;quot;Enter name&amp;quot;, text: $userViewModel.name ?? &amp;quot;&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we try to pass in a property of our model to a child view which needs a binding, we&amp;#x27;ll get the compiler error &lt;code&gt;&amp;quot;Cannot find &amp;#x27;$userViewModel&amp;#x27; in scope&amp;quot;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;And that&amp;#x27;s where the &lt;code&gt;@Bindable&lt;/code&gt; property wrapper comes into play. &lt;code&gt;@Bindable&lt;/code&gt; allows us to create &lt;strong&gt;bindings to properties that are part of a model&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ChildView: View {
    @Bindable var userViewModel: UserViewModel

    var body: some View {
        VStack {
            TextField(&amp;quot;Enter name&amp;quot;, text: $userViewModel.name ?? &amp;quot;&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, &lt;code&gt;TextField&lt;/code&gt; can modify &lt;code&gt;userViewModel.name&lt;/code&gt;, and SwiftUI will properly update the view hierarchy.&lt;/p&gt;</content:encoded><category>property wrappers</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick guide on home screen quick actions for SwiftUI]]></title><description><![CDATA[Learn how to provide users with direct access to app functionality from the home screen.]]></description><link>https://tanaschita.com/ios-home-screen-quick-actions</link><guid isPermaLink="false">https://tanaschita.com/ios-home-screen-quick-actions</guid><pubDate>Sun, 23 Feb 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Quick actions allow us to give users access to our app&amp;#x27;s functionality directly from the home screen, when they touch and hold the app icon.&lt;/p&gt;&lt;p&gt;For example, the native iOS Photos app offers quick actions to jump directly to recent photos, favorites, or search.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4c7bedfc915115da345586e356f29d04/ac685/photos-app.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:43.733333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsTAAALEwEAmpwYAAABcklEQVQoz2P4BQM/f/z4////qSWTZ+bGFuWVLmoumV2WYO0SGplclJBRHpyYX9jS9+v3719IgAFN8541K5ZNnlhV37O2pWHZwpz0vPqGtglxERnTGvtKOib9/PULu+YfYM1dvVMCQuPDI1MbW2dMWLC8va1nzoLlHW0T18xZVts34/uPH7+RLEdo/v79+////yurmgzdIqyioufOWXDi8In9Bw6dO3/x6o3bezbv7pu1+MvXb/g0l5fXe/ilBmemLl2y6vzZi+fPX7p1687du/ev3rzdN3fp5y9f/+DRXFHZyM4hyssrNWPWgksXr1y/duvB/Uf3Hzy6cPl654yFhDRXN3Pwy2oZ2Kxbv+XatZsXLly+cuX6g4dPzly+1jZtwecvX7BrhgTY6gVLi8LievPLLu4/9OLajSdXrz988Pj+oycXL1xunjwHp80/wTYf7OzvkFJpltPotXKYGho1s7B02Yw5M2YtDEotSixrQo2pXwAgjKMzud75xQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Quick actions of the native iOS Photos app.&quot; title=&quot;Quick actions of the native iOS Photos app.&quot; src=&quot;/static/4c7bedfc915115da345586e356f29d04/2e9ed/photos-app.png&quot; srcSet=&quot;/static/4c7bedfc915115da345586e356f29d04/12d18/photos-app.png 375w,/static/4c7bedfc915115da345586e356f29d04/44507/photos-app.png 750w,/static/4c7bedfc915115da345586e356f29d04/2e9ed/photos-app.png 1500w,/static/4c7bedfc915115da345586e356f29d04/e90d0/photos-app.png 2250w,/static/4c7bedfc915115da345586e356f29d04/ac685/photos-app.png 2570w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Quick actions of the native iOS Photos app.&lt;/div&gt;&lt;p&gt;Let&amp;#x27;s look at how we can implement quick actions in our own apps.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding quick actions&lt;/h3&gt;&lt;p&gt;There are two ways to create quick actions:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Static quick actions&lt;/strong&gt; – defined in the app&amp;#x27;s &lt;code&gt;Info.plist&lt;/code&gt;, these are best for features that do not rely on app state.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dynamic quick actions&lt;/strong&gt; – created programmatically, these adapt to app state or user data.&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;Static quick actions&lt;/h4&gt;&lt;p&gt;Static quick actions are straightforward to define. For instance, a &amp;quot;Favorites&amp;quot; action can be added by including the following configuration in the &lt;code&gt;Info.plist&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;UIApplicationShortcutItems&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;dict&amp;gt;
        &amp;lt;key&amp;gt;UIApplicationShortcutItemType&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;favoritesAction&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;UIApplicationShortcutItemIconSymbolName&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;heart&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;UIApplicationShortcutItemTitle&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;Favorites&amp;lt;/string&amp;gt;
    &amp;lt;/dict&amp;gt;
&amp;lt;/array&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here’s what each key represents:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;UIApplicationShortcutItemType&lt;/code&gt; – A unique identifier for the action, used later when handling the action.&lt;/li&gt;&lt;li&gt;&lt;code&gt;UIApplicationShortcutItemIconSymbolName&lt;/code&gt; – Specifies an SF Symbol for the action’s icon.&lt;/li&gt;&lt;li&gt;&lt;code&gt;UIApplicationShortcutItemTitle&lt;/code&gt; – Displays the action’s title.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To add a subtitle, include the &lt;code&gt;UIApplicationShortcutItemSubtitle&lt;/code&gt; key. Titles and subtitles can be localized using a &lt;code&gt;InfoPlist.strings&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;key&amp;gt;UIApplicationShortcutItemTitle&amp;lt;/key&amp;gt;
&amp;lt;string&amp;gt;QUICK_ACTION_FAVORITES_TITLE&amp;lt;/string&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a matching key in the &lt;code&gt;InfoPlist.strings&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;quot;QUICK_ACTION_FAVORITES_TITLE&amp;quot; = &amp;quot;Favorites&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Dynamic quick actions&lt;/h4&gt;&lt;p&gt;Dynamic quick actions are created programmatically and offer flexibility. For instance, we could add a &amp;quot;Favorites&amp;quot; action only if the user has saved favorites:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func updateShortcutItems() {
    guard !favorites.isEmpty else {
        UIApplication.shared.shortcutItems = []
        return
    }

    let favoritesAction = UIApplicationShortcutItem(
        type: &amp;quot;favoritesAction&amp;quot;,
        localizedTitle: &amp;quot;Favorites&amp;quot;,
        localizedSubtitle: nil,
        icon: UIApplicationShortcutIcon(systemImageName: &amp;quot;heart&amp;quot;),
        userInfo: [:])
    UIApplication.shared.shortcutItems = [favoritesAction]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This method can be called whenever the app enters the background to keep the quick actions up-to-date.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some Scene {
    @Environment(\.scenePhase) private var scenePhase

    WindowGroup {
        ContentView()
    }
    .onChange(of: scenePhase) { (newScenePhase) in
        if newScenePhase == .background {
            updateShortcutItems()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Handling quick actions&lt;/h3&gt;&lt;p&gt;Currently, SwiftUI does not offer a direct way to handle quick action events. Instead, we use UIKit&amp;#x27;s &lt;code&gt;SceneDelegate&lt;/code&gt; to manage these interactions.&lt;/p&gt;&lt;p&gt;It provides the &lt;code&gt;windowScene(_:performActionFor:completionHandler:)&lt;/code&gt; delegate method which is called when the app is currently in the background and a user selects a quick action.&lt;/p&gt;&lt;p&gt;If the app isn’t already loaded, it’s launched and the shortcut item is passed in through the &lt;code&gt;connectionOptions&lt;/code&gt; parameter of the &lt;code&gt;scene(_:willConnectTo:options:)&lt;/code&gt; delegate method.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class SceneDelegate: NSObject, UIWindowSceneDelegate {

    func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -&amp;gt; Void) {
        handleShortcutItem(shortcutItem)
        completionHandler(true)
    }

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let shortcutItem = connectionOptions.shortcutItem {
            handleShortcutItem(shortcutItem)
        }
    }

    func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) {
        if shortcutItem.type == &amp;quot;favoritesAction&amp;quot; {
            // Show favorites
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the &lt;code&gt;handleShortcutItem&lt;/code&gt; method, we check the shortcut item&amp;#x27;s &lt;code&gt;type&lt;/code&gt; property to determine which quick action was tapped.&lt;/p&gt;&lt;p&gt;To learn more about the SceneDelegate, check out the article on &lt;a href=&quot;/swiftui-scenedelegate/&quot;&gt;how to use the SceneDelegate in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;When debugging quick home screen actions from an app launch, we need a way to quit the app without loosing Xcode&amp;#x27;s debugger. Check out the article on &lt;a href=&quot;/xcode-debugging-app-restart/&quot;&gt;how to debug iOS features that require app start from external actions&lt;/a&gt; to find out how to do that.&lt;/p&gt;</content:encoded><category>ios</category><category>swift</category><category>swiftui</category></item><item><title><![CDATA[How to use SceneDelegate in SwiftUI]]></title><description><![CDATA[Learn how to integrate SceneDelegate with a SwiftUI-based application.]]></description><link>https://tanaschita.com/swiftui-scenedelegate</link><guid isPermaLink="false">https://tanaschita.com/swiftui-scenedelegate</guid><pubDate>Sun, 16 Feb 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI simplifies app development by abstracting away much of the boilerplate code associated with UIKit. However, certain features, such as handling home screen quick actions, still require &lt;code&gt;SceneDelegate&lt;/code&gt; from UIKit.&lt;/p&gt;&lt;p&gt;This article is a guide to using &lt;code&gt;SceneDelegate&lt;/code&gt; in a SwiftUI-based application. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;To incorporate &lt;code&gt;SceneDelegate&lt;/code&gt; in a SwiftUI app, follow these steps:&lt;/p&gt;&lt;h3&gt;1. Create a SceneDelegate class&lt;/h3&gt;&lt;p&gt;Add a new Swift file and define your &lt;code&gt;SceneDelegate&lt;/code&gt; class. The example below shows an exampe of the &lt;code&gt;SceneDelegate&lt;/code&gt; for handling home screen quick actions:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class SceneDelegate: NSObject, UIWindowSceneDelegate {

    func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -&amp;gt; Void) {
        handleShortcutItem(shortcutItem)
        completionHandler(true)
    }

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let shortcutItem = connectionOptions.shortcutItem {
            handleShortcutItem(shortcutItem)
        }
    }

    func handleShortcutItem(_ shortcutItem: UIApplicationShortcutItem) {
        if shortcutItem.type == &amp;quot;favoritesAction&amp;quot; {
            // Show favorites
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Define the AppDelegate&lt;/h3&gt;&lt;p&gt;We need an &lt;code&gt;AppDelegate&lt;/code&gt; to connect the &lt;code&gt;SceneDelegate&lt;/code&gt; with the app’s lifecycle. Create an &lt;code&gt;AppDelegate.swift&lt;/code&gt; file:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        configurationForConnecting connectingSceneSession: UISceneSession,
        options: UIScene.ConnectionOptions
    ) -&amp;gt; UISceneConfiguration {
        let configuration = UISceneConfiguration(name: &amp;quot;Default Configuration&amp;quot;, sessionRole: connectingSceneSession.role)
        configuration.delegateClass = SceneDelegate.self
        return configuration
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It ensures that the system uses our &lt;code&gt;SceneDelegate&lt;/code&gt; when creating new scenes.&lt;/p&gt;&lt;h3&gt;3. Modify the SwiftUI App structure&lt;/h3&gt;&lt;p&gt;The final step is connecting the AppDelegate to the SwiftUI app lifecycle by modifying the main app file as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI

@main
struct MyApp: App {
    // Integrate the AppDelegate
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a setup of &lt;code&gt;@UIApplicationDelegateAdaptor&lt;/code&gt; as shown above, SwiftUI instantiates the delegate automatically and calls the delegate’s methods in response to life cycle events.&lt;/p&gt;</content:encoded><category>swiftui</category><category>uikit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Implementing Face ID authentication in SwiftUI]]></title><description><![CDATA[Learn how to add biometric authentication with passcode fallback to secure your app.]]></description><link>https://tanaschita.com/ios-local-authentication</link><guid isPermaLink="false">https://tanaschita.com/ios-local-authentication</guid><pubDate>Sun, 09 Feb 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple&amp;#x27;s Local Authentication framework enables us to implement biometric authentication, such as Face ID and Touch ID, allowing users to securely access sensitive app data. In this article, we’ll explore how to integrate Face ID using Swift and SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Setting up LocalAuthentication framework&lt;/h3&gt;&lt;p&gt;As a first step, we need to add the &lt;code&gt;NSFaceIDUsageDescription&lt;/code&gt; key in the app’s Info.plist file. This key is mandatory, and without it, the system will prevent Face ID from being used. The value should clearly describe why Face ID access is required.&lt;/p&gt;&lt;p&gt;After that, we can import and use the LocalAuthentication framework, which provides access to biometric authentication functions and error handling.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import LocalAuthentication
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Checking if biometry is available&lt;/h3&gt;&lt;p&gt;Before authenticating the user, an essential step is to check if biometrics are available on the device:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func isAuthenticationAvailable() -&amp;gt; Bool {
    let context = LAContext()
    var error: NSError?

    return context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &amp;amp;error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The value &lt;code&gt;deviceOwnerAuthentication&lt;/code&gt; includes passcode authentication if biometry is not available.&lt;/p&gt;&lt;h2&gt;Starting the authentication process&lt;/h2&gt;&lt;p&gt;Once we know authentication is available, we can initiate the process by calling the &lt;code&gt;evaluatePolicy&lt;/code&gt; method, which prompts the user to authenticate.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, error) in
    DispatchQueue.main.async {
        if success {
            // Authentication succeeded.
        } else {
            // Handle the error (e.g., user canceled, authentication failed).
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Integrating with SwiftUI views&lt;/h3&gt;&lt;p&gt;To integrate biometric authentication into a SwiftUI app, we can call the authentication function within a SwiftUI view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @State private var isAuthenticated = false

    var body: some View {
        VStack {
            if isAuthenticated {
                Text(&amp;quot;Welcome to your secure data!&amp;quot;)
            } else {
                Button(&amp;quot;Authenticate&amp;quot;) {
                    authenticate()
                }
            }
        }
    }

    private func authenticate() {
        authService.authenticate { success, error in
            if success {
                isAuthenticated = true
            } else {
                // Handle error
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Handling errors&lt;/h3&gt;&lt;p&gt;Authentication may fail due to various reasons, such as the user canceling the prompt or the system being unable to recognize the face. We can handle these errors by evaluating the error as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let error = error as? LAError {
    switch error.code {
    case .userCancel:
        // Handle user cancellation
    case .authenticationFailed:
        // Handle failed authentication
    default:
        // Handle other errors
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Testing the authentication on a simulator&lt;/h3&gt;&lt;p&gt;To test the authentication flow, we don&amp;#x27;t necessarily need a device. We can simulate success and failure directly in simulator by choosing &lt;code&gt;Features -&amp;gt; Face ID&lt;/code&gt; in the menu and then selecting the behaviour we want to simulate.&lt;/p&gt;&lt;h3&gt;Good to know&lt;/h3&gt;&lt;p&gt;The iOS app never gains any access to the actual authentication data like fingerprint images. It is stored by an isolated security processor called Secure Enclave, which manages this data out of reach even for the operating system.&lt;/p&gt;&lt;p&gt;So when we ask the Local Authentication framework to evaluate, the framework asks the Secure Enclave to carry out the operation. Afterwards, we only receive a Boolean result and an optional error indicating success or failure.&lt;/p&gt;</content:encoded><category>authentication</category><category>security</category><category>swift</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[SF Symbols guide for SwiftUI and UIKit]]></title><description><![CDATA[Learn how to use SF Symbols for consistent, scalable icons in iOS.]]></description><link>https://tanaschita.com/ios-sf-symbols</link><guid isPermaLink="false">https://tanaschita.com/ios-sf-symbols</guid><pubDate>Sun, 02 Feb 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SF Symbols is Apple&amp;#x27;s library of vector-based icons that integrate seamlessly into iOS, providing a unified look across Apple platforms. Each symbol is vector-based, meaning it scales smoothly at different sizes and weights, making it ideal for modern app design.&lt;/p&gt;&lt;p&gt;In this guide, we’ll explore how to incorporate SF Symbols into SwiftUI and UIKit projects, from adding basic icons to customizing their scale, weight, and color.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Downloading the SF Symbols app&lt;/h3&gt;&lt;p&gt;We can browse the full SF Symbols collection using the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/sf-symbols/&quot;&gt;SF Symbols app&lt;/a&gt;, available for free from Apple&amp;#x27;s developer site. This app allows us inspect all available symbols in different weights and scales, and copy the symbol names to use in our code.&lt;/p&gt;&lt;h3&gt;Using SF Symbols in iOS&lt;/h3&gt;&lt;p&gt;Adding SF Symbols is straightforward in both SwiftUI and UIKit. It&amp;#x27;s is as simple as using an &lt;code&gt;Image&lt;/code&gt; view with the &lt;code&gt;systemName&lt;/code&gt; initializer:&lt;/p&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Image(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UIImage(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Customizing SF Symbols&lt;/h3&gt;&lt;p&gt;One of the strengths of SF Symbols is their ability to adapt to different styles, including scale, weight, and color.&lt;/p&gt;&lt;h4&gt;1. Adjusting scale and weight&lt;/h4&gt;&lt;p&gt;To adjust size and weight, we can use the &lt;code&gt;font&lt;/code&gt; modifier on the image:&lt;/p&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Image(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
    .font(Font.system(size: 60, weight: .bold))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In UIKit, we can use the &lt;code&gt;UIImage.SymbolConfiguration&lt;/code&gt; type. The &lt;code&gt;applying&lt;/code&gt; method can be used to combine different configurations into one:&lt;/p&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var config = UIImage.SymbolConfiguration(weight: .bold)
config = config.applying(UIImage.SymbolConfiguration(font: .systemFont(ofSize: 42.0))

let image = UIImage(systemName: &amp;quot;cloud.moon.rain&amp;quot;, withConfiguration: config)
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. Applying colors&lt;/h4&gt;&lt;p&gt;SF Symbols support different layers, allowing us to control colors for each layer using various rendering modes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Monochrome&lt;/strong&gt;: Applies a single color to the entire symbol.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Hierarchical&lt;/strong&gt;: Assigns different opacities to each layer, creating a subtle depth effect.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Palette&lt;/strong&gt;: Assigns colors to each layer, creating a multicolored look.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multicolor&lt;/strong&gt;: Uses Apple’s intrinsic colors to enhance symbol meaning in supported icons.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For example, the &lt;code&gt;cloud.moon.rain&lt;/code&gt; symbol consists of three layers: the clouds, the moon and the rain are each represented by one layer. Let&amp;#x27;s see how each rendering mode affects its appearance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Monochrome&lt;/strong&gt; applies a single color to the entire symbol:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:100px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4e879f393c164c84ce93418ad0298c5e/58f13/monochrome.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:75.99999999999999%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABjElEQVQ4y5VUy07CQBTtn/orujBxYaIxrowLE03UBIkS48KFj6CEGFHBUizKqxQoDVAeLaXQ9piZONLaFvUkk850Zs499zUcQuC67OvSwebzfRdR4PxEfgLbmQ+CmeOgY0wCZxcSRimY2g420gVspd/omhhxPOfZHS6KjFy4LDWxny0jr/ZxVVEQFyQcvlYQJsKnkFnwWtp+EHHMV8GrGvZeSlg6TUEeGojla7goNlDqDdEYjqMVMlUE1xUFO5l3n2WibDXJI1lrI9PsYvkmiyO++n3PR0gWLB7l3ggrtzlIA52uLdumySDQrSnW7vI4yJURL0gwZ3bAbc6bUYLdpw/kFC2yLEhiskovMpEBhYRsPSXgTKzjRJAQEySqJiHK2EwXkBDrATcjY8h+tkZjGsfHRoeqSUkqjV1V00PrNdRl70ZbN2nJMJwXZUy+4uVEdE+gsL2EqmGib1qUSB4Y0EwLz60u7iU1QLqwU7xWSWd4kyOofQwm0//3cthD4PzxYQgS/iD1uvZb7BjhJ8rNiYQc8JHBAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Symbol with rendering mode monochrome.&quot; title=&quot;Symbol with rendering mode monochrome.&quot; src=&quot;/static/4e879f393c164c84ce93418ad0298c5e/58f13/monochrome.png&quot; srcSet=&quot;/static/4e879f393c164c84ce93418ad0298c5e/58f13/monochrome.png 100w&quot; sizes=&quot;(max-width: 100px) 100vw, 100px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Image(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
      .foregroundColor(.teal)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UIImage(systemName: &amp;quot;cloud.moon.rain&amp;quot;)?.withTintColor(.systemTeal)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Hierarchical&lt;/strong&gt; applies one color to the symbol, but using different opacity for each layer:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:100px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/e28e1efd226369b9c7e934c98f910937/58f13/hierarchical.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:74%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABbklEQVQ4y41UyU7DMBTML/MLXBEnQCAhJA7cEHBEcKsERZSliIouaSuaIFDXpHSNkziDntME140Llp787DjjmbfYwJoRkUWRMCx8vvCTtTqMFRAFQLasM2sB5YNZTHpTD5XecOlClanx+xMywfpTT8x+yJFrtXH30dUqUQCXwRqDEbbzZewVatgt1HDd/MLUD1DqOJiwQBvHFckJm81cCS13LNY0b1w94aJiwxpO8NJ2UOq4Qn4mQzXwR8UGTt8s4bOQi9mdMZyVLcGWwnBrd2H2v/WAidQbq4vD57q2lI6LDcF0OGeZGU8BQx5vXtY/cV6xhWzKqjNjGMw8uHOGpjPGVr6MMfPTeKuZNtRbSOLJ67uQdvBoYv/BFDPZzn0V1V4sMyGgLRv1gxeEqe+HIQLO0zWXy0VX2DJLAiNZBEI28nwRAtrnccFqu8VQ+1ZmSoEPFlmmGkzq79+tp7aT3DF/PQprY6h9COSsagB/AD3ikeasbc5cAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Symbol with rendering mode hierarchical.&quot; title=&quot;Symbol with rendering mode hierarchical.&quot; src=&quot;/static/e28e1efd226369b9c7e934c98f910937/58f13/hierarchical.png&quot; srcSet=&quot;/static/e28e1efd226369b9c7e934c98f910937/58f13/hierarchical.png 100w&quot; sizes=&quot;(max-width: 100px) 100vw, 100px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Image(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
    .foregroundColor(.teal)
    .symbolRenderingMode(.hierarchical)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UIImage.SymbolConfiguration(hierarchicalColor: .systemTeal)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Palette&lt;/strong&gt; applies two or more colors to the symbol, using one color per layer. Specifying two colors for a symbol with three levels will use the same color for the secondary and tertiary layer.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:120px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/311bea7e6da93690c64b64aae29e0f3d/c3f41/palette.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:75%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAABnUlEQVQ4y4WU0UvbUBTG80/vbf/AntwYjvnUDRmbgg8OBsrYkKGgdQv2waW2GlvMau3a2qYkzZJ770/aJNLc3MwPwj05J+c73+GeE0spxQL5qWPhX/1Gt1d9QggsE1kVgR435ViFZI1MKoWQ6bOAFIr5ONGKFYtaVcpKCqSi/vIOe33wSK6kKuVZ/yM76PSp2Vd8u+3T+erjbk843xoZW81RIhTZufO7y2bDxZsEfOl4PF+3ubz0udqZ0N6dcPszYNr996g+5y20LDOvO57x4ui8UPnkz1/WThz2Wz36hwHfn3n06kE1Ya7Qj2LWjh3aI7900wu8OnSof7hjcBoa45aurj2csvGrvbTDOCESkihJiIXA8wNeHzcJo8RIVlKYBz9f3PDWvuH9mUvNblFreLxrdHlTd2gOp9kQm2fUMDZpcD46IJjfEyoI708JZi5K6+bpW86GWyYzGH6C6BriHox3Yd6EsIVMb8HYrka4OotJujWDj6jASe3xHmr6I4vLyt23qnZymSTj1F6KigorWgVLYzP/CPL33F71aYQPlj+OQY28skwAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Symbol with rendering mode palette.&quot; title=&quot;Symbol with rendering mode palette.&quot; src=&quot;/static/311bea7e6da93690c64b64aae29e0f3d/c3f41/palette.png&quot; srcSet=&quot;/static/311bea7e6da93690c64b64aae29e0f3d/c3f41/palette.png 120w&quot; sizes=&quot;(max-width: 120px) 100vw, 120px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Image(systemName: &amp;quot;cloud.moon.rain&amp;quot;)
      .foregroundStyle(.teal, .purple, .yellow)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;UIImage.SymbolConfiguration(paletteColors: [.systemTeal, .systemPurple, .systemYellow])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Multicolor&lt;/strong&gt; applies system-defined intrinsic colors to some symbols to enhance meaning. It will not have an effect on every symbol.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:600px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ddf7a521089e9b9fef21c0da6c650acb/ff59c/multicolor.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:26.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA80lEQVQY002O3U7CQBCF+/ze+CA+A3ojXphwYSKBYoO1QBDQQrfttt2f+cy20DjJycx8mZwzkXeOVFvy2QzSNY6hXo8Fk6cXWMzxAYjnqC3Z/hPZPg8M4VB3PLwt0etJfxN5EXbasNt+I1U5Gk53OfePM6rT6UqEvHWkvzla7Ud2rDvupu/M07gnUdu2FOczF6WomqaHIaTTNbquMN6DCN57VFFQFIqLqvs9lOk6mlLRdHYwtNay2WxJkoSyLHtojeEjSYjjmJ/rh865ga1iVqt4NMyyjMVySZZ9ISJE3J4XGXtQCAo9GN3Y//kmY0zPg0LIHxwDf4cDA7Q8AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Symbol with rendering mode multicolor.&quot; title=&quot;Symbol with rendering mode multicolor.&quot; src=&quot;/static/ddf7a521089e9b9fef21c0da6c650acb/ff59c/multicolor.png&quot; srcSet=&quot;/static/ddf7a521089e9b9fef21c0da6c650acb/12d18/multicolor.png 375w,/static/ddf7a521089e9b9fef21c0da6c650acb/ff59c/multicolor.png 600w&quot; sizes=&quot;(max-width: 600px) 100vw, 600px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Image(systemName: &amp;quot;calendar.badge.plus&amp;quot;)
      .foregroundColor(.teal)
      .symbolRenderingMode(.multicolor)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;UIImage.SymbolConfiguration.preferringMulticolor()
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Integrating SF Symbols with Text&lt;/h3&gt;&lt;p&gt;SF Symbols are designed not to be only used as standalone icons, but also together with text.&lt;/p&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Label(&amp;quot;Welcome&amp;quot;, systemImage: &amp;quot;tortoise&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let attachment = NSTextAttachment()
attachment.image = UIImage(systemName: &amp;quot;tortoise&amp;quot;)

let attributedString = NSMutableAttributedString(attachment: attachment)
attributedString.append(NSAttributedString(string: &amp;quot;Welcome&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally to sizing symbols with fonts, we can configure their scale that sets the symbols size compared to its neighboring text. Available scales are: &lt;code&gt;.small&lt;/code&gt;, &lt;code&gt;.medium&lt;/code&gt; and &lt;code&gt;.large&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;SwiftUI&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;.imageScale(.large)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;UIKit&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UIImage.SymbolConfiguration(scale: .large)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Checking symbol availability&lt;/h3&gt;&lt;p&gt;Not all symbols are supported in every iOS version. Use the SF Symbols app to check each symbol’s compatibility with different iOS versions. Symbols introduced in later iOS versions may require alternative options or fallbacks.&lt;/p&gt;&lt;p&gt;If some symbol is not available for our iOS version, we can export it from the SF Symbols app as an SVG template and bundle it with our iOS app. Note that it makes the symbol available, but not the additional rendering modes.&lt;/p&gt;&lt;h3&gt;Creating custom symbols&lt;/h3&gt;&lt;p&gt;If we need a unique icon that SF Symbols doesn’t offer, Apple allows custom symbols. Custom symbols can maintain the same scalability and adaptability as native SF Symbols if created correctly. Check out &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/uikit/uiimage/creating_custom_symbol_images_for_your_app&quot;&gt;Apple&amp;#x27;s guide on creating custom symbol images&lt;/a&gt; for more details.&lt;/p&gt;</content:encoded><category>swiftui</category><category>uikit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Learn to debug iOS features that require app start from external actions in Xcode]]></title><description><![CDATA[How to debug features like deep linking or home screen quick actions.]]></description><link>https://tanaschita.com/xcode-debugging-app-restart</link><guid isPermaLink="false">https://tanaschita.com/xcode-debugging-app-restart</guid><pubDate>Sun, 26 Jan 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Debugging iOS features that require app starts from external actions - such as deep linking or home screen quick actions - can be challenging. By default, when running an app in Xcode, the debugger automatically attaches itself. However, once the app is killed on the simulator or device, the debugger detaches, making it difficult to test features that require the app to be launched from an external action.&lt;/p&gt;&lt;p&gt;Xcode offers a solution: the &amp;quot;&lt;em&gt;Wait for executable to be launched&lt;/em&gt;&amp;quot; scheme option. This allows us to attach the debugger to the app even when launching it manually.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Steps to enable the debugger for manual launch&lt;/h3&gt;&lt;p&gt;To activate the &lt;code&gt;Wait for executable to be launched&lt;/code&gt; feature:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Edit the scheme:&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;Go to the Xcode menu and select &lt;code&gt;Product &amp;gt; Scheme &amp;gt; Edit Scheme&lt;/code&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;ol start=&quot;2&quot;&gt;&lt;li&gt;&lt;strong&gt;Enable the setting:&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;Under the &lt;em&gt;Info&lt;/em&gt; tab in the &lt;em&gt;Launch&lt;/em&gt; section, select the option: &lt;em&gt;&amp;quot;Wait for executable to be launched&amp;quot;&lt;/em&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;ol start=&quot;3&quot;&gt;&lt;li&gt;&lt;strong&gt;Run the app&lt;/strong&gt;:&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;li&gt;Start running the app in Xcode. The app won’t automatically launch as it typically does. Instead, Xcode will pause and wait for us to manually launch the app from the simulator or device.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Debugging Features After Manual Launch&lt;/h3&gt;&lt;p&gt;Once the app is manually launched, Xcode will automatically attach the debugger. This setup is especially useful for testing:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;Deep Linking / Universal Links&lt;/em&gt;: Tap a deep link to launch the app and verify the flow.&lt;/li&gt;&lt;li&gt;&lt;em&gt;Home Screen Quick Actions&lt;/em&gt;: Trigger an action and ensure the app behaves as expected.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By keeping the debugger attached during these scenarios, we can use breakpoints, inspect variables, and view console logs to diagnose any issues.&lt;/p&gt;</content:encoded><category>xcode</category><category>testing</category><category>ios</category></item><item><title><![CDATA[Customizing the appearance of the NavigationStack title in SwiftUI]]></title><description><![CDATA[Learn how to create a custom navigation bar title view in SwiftUI using NavigationStack.]]></description><link>https://tanaschita.com/switui-navigationstack-customize-title</link><guid isPermaLink="false">https://tanaschita.com/switui-navigationstack-customize-title</guid><pubDate>Sun, 19 Jan 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with &lt;code&gt;NavigationStack&lt;/code&gt;, we have the possibility to customize the appearance of the navigation bar, including the title. This article explores two approaches to creating custom titles using SwiftUI’s &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt; and &lt;code&gt;toolbar(content:)&lt;/code&gt; modifiers.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The Default Title Behavior&lt;/h3&gt;&lt;p&gt;In &lt;code&gt;NavigationStack&lt;/code&gt;, a title is displayed when the &lt;code&gt;navigationTitle(_:)&lt;/code&gt; modifier is used. By default, the system employs a large title that transitions to an inline title when scrolling:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;NavigationStack {
    VStack {
        // Content...
    }
    .navigationTitle(&amp;quot;Inline Title&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we want to move beyond the default behavior, we can use the following customization techniques.&lt;/p&gt;&lt;h3&gt;Customizing with &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt; modifier lets us control how the navigation title appears. Setting the mode to &lt;code&gt;.inline&lt;/code&gt; ensures that the title remains compact and inline at all times, even when scrolling. This creates a consistent look across the app, particularly for minimalistic designs.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;NavigationStack {
    VStack {
        // Content...
    }
    .navigationTitle(&amp;quot;Inline Title&amp;quot;)
    .navigationBarTitleDisplayMode(.inline)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Adding Custom Title Views with &lt;code&gt;toolbar(content:)&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;To fully customize the appearance of the navigation title, the &lt;code&gt;toolbar(content:)&lt;/code&gt; modifier allows replacing the default title with any SwiftUI view. By specifying a &lt;code&gt;ToolbarItem&lt;/code&gt; with a placement of &lt;code&gt;.principal&lt;/code&gt;, we can override the default title and insert completely custom layouts:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;NavigationStack {
    VStack {
        // Content...
    }
    .navigationTitle(&amp;quot;Custom Title&amp;quot;)
    .navigationBarTitleDisplayMode(.inline)
    .toolbar {
        ToolbarItem(placement: .principal) {
            Image(systemName: &amp;quot;photo.fill&amp;quot;)
                .font(.title)
                .foregroundColor(.blue)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of an &lt;code&gt;Image&lt;/code&gt;, we can use any SwiftUI view.&lt;/p&gt;</content:encoded><category>navigation</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to use cryptographic hash functions in CryptoKit for iOS security]]></title><description><![CDATA[Learn how to implement cryptographic hash functions in Swift.]]></description><link>https://tanaschita.com/swift-hash-functions</link><guid isPermaLink="false">https://tanaschita.com/swift-hash-functions</guid><pubDate>Sun, 12 Jan 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cryptographic hash functions are essential tools in secure application development, commonly used for verifying data integrity, securely storing passwords, and generating unique identifiers. Apple&amp;#x27;s CryptoKit framework offers a range of hashing functions that are efficient, secure, and easy to implement within Swift.&lt;/p&gt;&lt;p&gt;In this guide, we’ll explore how to work with CryptoKit’s hash functions, such as SHA-256 and SHA-512, to improve the security of your iOS apps.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is a cryptographic hash function?&lt;/h3&gt;&lt;p&gt;A cryptographic hash function generates a fixed-length hash (or digest) from input data of any size. This hash acts like a digital fingerprint, uniquely identifying the input without revealing the original data. Key features of a secure cryptographic hash function include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;one-way function&lt;/em&gt; - meaning that it&amp;#x27;s practically irreversible&lt;/li&gt;&lt;li&gt;&lt;em&gt;deterministic&lt;/em&gt; - meaning that the same message always results in the same hash value&lt;/li&gt;&lt;li&gt;&lt;em&gt;unique&lt;/em&gt; - meaning that it&amp;#x27;s practically not possible to find two different messages with the same hash value&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These characteristics make hash functions ideal for storing hashed passwords, validating data integrity, and ensuring message authenticity.&lt;/p&gt;&lt;p&gt;For example, when a user enters a password into our app to login, we hash the password and send it to the server for verification. The passwords stored on the server are also computed hash values of the original passwords. This way, we never store the original password or send it over the network but still are able to verify it.&lt;/p&gt;&lt;h3&gt;Hash functions in CryptoKit&lt;/h3&gt;&lt;p&gt;CryptoKit provides several hashing algorithms, the main two algorithms are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;SHA-256&lt;/code&gt;: A commonly used 256-bit hash function providing a secure balance between performance and cryptographic strength.&lt;/li&gt;&lt;li&gt;&lt;code&gt;SHA-512&lt;/code&gt;: A 512-bit version of SHA, offering a higher level of security, typically used for applications requiring greater protection.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For most iOS applications, SHA-256 is a preferred choice because of its strength and efficiency.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how we can hash a password with the SHA-256 algorithm:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func hashPassword(_ password: String) -&amp;gt; String {
    let hash = SHA256.hash(data: Data(password.utf8))
    return hash.compactMap { String(format: &amp;quot;%02x&amp;quot;, $0) }.joined()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;compactMap&lt;/code&gt; line in this function is used to convert each byte in the SHA-256 hash into a hexadecimal &lt;code&gt;String&lt;/code&gt; representation, returning a single, readable hash string.&lt;/p&gt;</content:encoded><category>cryptokit</category><category>security</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Preparing for a technical iOS job interview with questions and answers in 2025 - book preview]]></title><description><![CDATA[An updated book version with a new chapter on Architecture & Design Patterns is now available.]]></description><link>https://tanaschita.com/book-ios-interview-questions-and-answers-preview</link><guid isPermaLink="false">https://tanaschita.com/book-ios-interview-questions-and-answers-preview</guid><pubDate>Mon, 06 Jan 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I just released a new book version of &lt;a href=&quot;/books/&quot;&gt;Preparing for a technical iOS job interview - with over 200 questions &amp;amp; answers&lt;/a&gt; including a new chapter on Architecture &amp;amp; Design Patterns.&lt;/p&gt;&lt;p&gt;This preview provides one example question and answer for every main chapter of the book. You can also download a preview &lt;a href=&quot;/books/&quot;&gt;here&lt;/a&gt; to see the full outline of the book.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Introduction&lt;/h3&gt;&lt;p&gt;Having been a freelance iOS developer for over 10 years, I participated in quite a few hiring interviews for various projects and have also been in a position of leading the technical interviews myself.&lt;/p&gt;&lt;p&gt;With this book, I&amp;#x27;d like to give you a guide for technical iOS questions you might be asked in such an interview for an iOS hiring position. It can also be used to get an overview on different iOS development topics and be an inspiration for deepening and expanding your knowledge on your iOS journey.&lt;/p&gt;&lt;p&gt;Each chapter starts with a questions overview that allows you to check your knowledge without seeing the answers. All questions are numerated so you can quickly find the answer if needed. The following topics are covered:&lt;/p&gt;&lt;h3&gt;Chapter: Swift&lt;/h3&gt;&lt;p&gt;Being the main programming language for the iOS platform, a deep knowledge in Swift is expected from an iOS developer. This chapter provides questions and answers on Swift and general programming language concepts.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer:&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Swift is a type-safe language. What does type safety mean, and why is it important?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Type safety means that the compiler enforces rules to prevent type errors by ensuring values are used consistently with their declared types. For example, if we declare a variable as an &lt;code&gt;Int&lt;/code&gt;, we cannot assign a &lt;code&gt;String&lt;/code&gt; value to it. The compiler checks for type mismatches during compilation and provides errors, helping developers catch and fix issues early in the development process.&lt;/p&gt;&lt;p&gt;This feature improves code reliability and reduces runtime errors because many potential bugs are identified before the app runs. Type safety also enhances code readability and maintainability, as the expected data types are clear from the code.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var age: Int = 10
age = &amp;quot;ten&amp;quot; // Compiler error: Cannot assign a value of type &amp;#x27;String&amp;#x27; to a variable of type &amp;#x27;Int&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By enforcing type safety, Swift encourages robust and predictable programming practices, making code safer and easier to debug.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: SwiftUI&lt;/h3&gt;&lt;p&gt;SwiftUI is Apple&amp;#x27;s main framework for building user interfaces for iOS. This chapter provides questions and answers on the main aspects of SwiftUI.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why is it advisable to avoid using &lt;code&gt;AnyView&lt;/code&gt; in SwiftUI when possible?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;SwiftUI uses a concept called &lt;em&gt;structural identity&lt;/em&gt;, where it relies on a view&amp;#x27;s type to efficiently determine when the view should be updated.&lt;/p&gt;&lt;p&gt;&lt;code&gt;AnyView&lt;/code&gt; is a type-erased wrapper that can hold any SwiftUI view. However, since it erases the view&amp;#x27;s type information, it diminishes SwiftUI&amp;#x27;s ability to efficiently track and update the view. This can lead to reduced performance, especially in complex view hierarchies.&lt;/p&gt;&lt;p&gt;Therefore, it is advisable to avoid using &lt;code&gt;AnyView&lt;/code&gt; whenever possible to maintain the performance benefits of SwiftUI&amp;#x27;s structural identity.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Xcode&lt;/h3&gt;&lt;p&gt;Being the main iOS development tool, a confident usage of Xcode is expected from an iOS developer. This chapter provides questions and answers on working and debugging with Xcode.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the purpose of a build scheme in Xcode?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;An Xcode build scheme organizes the settings and configurations for development actions such as building, running, testing, profiling, and archiving a project. A scheme determines which targets to build and how they are built, allowing developers to manage workflows efficiently.&lt;/p&gt;&lt;p&gt;Only one scheme can be active at a time. Developers can customize default schemes or create new ones to handle specific tasks, such as creating different builds (e.g., debug or release) or passing unique launch arguments for testing.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b8837eda7fd314183e8161e3a8ca8c3a/fc2f5/xcode-scheme-edit.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:56.266666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABtUlEQVQoz42T247TMBiE8xpISNscfMrBdmInzaltdsV2l4IiRFUBb8IVV1zwAjzvIHtplAqQuPhky56M5x8pQWom1ONb0KIHLbZomgaHwwF930MpBa01TNPB2BrWGhhjYK31q1ISZVl6qqqClBKBePcN5eefePX4A6+nrxBpulBICaU1bD9h2O2xbVu0XYd+GDyqqsG9Vng9ZRxBpmuM4w6UUDDKECUJNnGETRSBco7SGHTjDh8vn/B+njE9PGDc73G4v4fdHXEXJwjjCGEce4K06vHm8RmpyEApR0yIN3WXhDFkRYFUurFb2LqGVBppniOhFJso9NorLkTApUWz7cCZeDFMyPKaE0mtoY2F3baorEGhFESW+VGd8Vp7F4YI3NyEMG/GmFgMrykTQsBz6ROWVYVcSm/q6ljrfEJnyLjwRoyKPwyv4jB66XR9vjb6iyG/TZjcfvi/bMJolfA313I9cXyzD/9J5Fk6dOkWGAfjHEwI39N6T1zfjIFQekPizkWOKCEIuEghRObhPMUwjDifz57j8Yh5nnG5XHA6ndBNTxjHcfkzKmNgdAF1+IDqy3ekdsIvmOpNAHh7edsAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode scheme edit&quot; title=&quot;Xcode scheme edit&quot; src=&quot;/static/b8837eda7fd314183e8161e3a8ca8c3a/2e9ed/xcode-scheme-edit.png&quot; srcSet=&quot;/static/b8837eda7fd314183e8161e3a8ca8c3a/12d18/xcode-scheme-edit.png 375w,/static/b8837eda7fd314183e8161e3a8ca8c3a/44507/xcode-scheme-edit.png 750w,/static/b8837eda7fd314183e8161e3a8ca8c3a/2e9ed/xcode-scheme-edit.png 1500w,/static/b8837eda7fd314183e8161e3a8ca8c3a/fc2f5/xcode-scheme-edit.png 1868w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Combine&lt;/h3&gt;&lt;p&gt;The Combine framework is Apple’s native solution for handling asynchronous events and data streams in Swift. It enables developers to write functional reactive code, streamlining tasks like chaining, transforming, and combining data. This chapter provides questions and answers on Combine and on functional reactive programming concepts in general.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the difference between a subject and a publisher in Combine?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Combine, a &lt;strong&gt;publisher&lt;/strong&gt; emits values over time and can finish or fail with an error. A &lt;strong&gt;subject&lt;/strong&gt; is a type of mutable publisher, meaning it can actively send new values after its initialization.&lt;/p&gt;&lt;p&gt;Essentially, subjects are a way to manually control the flow of values in a publisher, whereas regular publishers emit values automatically based on their data source.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Server Communication&lt;/h3&gt;&lt;p&gt;Server communication is part of almost every iOS application. This chapter provides questions and answers on general computer networking concepts and on specific iOS networking topics.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is OAuth, and why is it used?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;OAuth is an open protocol for secure authorization, allowing users to grant third-party services limited access to their resources without sharing credentials.&lt;/p&gt;&lt;p&gt;The core idea is the use of access tokens as intermediaries. When a third-party app requests access to a user&amp;#x27;s resources, it obtains an access token from the main service through a secure flow. This token grants limited permissions, ensuring:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The user&amp;#x27;s credentials (e.g., password) remain private and are not shared with the third party.&lt;/li&gt;&lt;li&gt;Access can be revoked at any time without affecting the user&amp;#x27;s main account.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;OAuth enhances security and user control over data sharing across applications and services.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Concurrency&lt;/h3&gt;&lt;p&gt;This chapter focuses on Swift&amp;#x27;s concurrency model with async/await. It also provides questions and answers on more low-level technologies like Grand Central Dispatch and Operations. Since they were used in iOS applications before async/await, knowledge around these technologies may be required for an iOS position.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the role of the await keyword in Swift&amp;#x27;s asynchronous programming?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;TThe &lt;code&gt;await&lt;/code&gt; keyword in Swift enables asynchronous functions to suspend execution until the result of another asynchronous function or task becomes available. This allows developers to write asynchronous code in a sequential and readable manner while the system manages underlying complexities, such as pausing and resuming execution. By combining &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;, Swift ensures clarity and efficiency in handling concurrent operations.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Persisting Data&lt;/h3&gt;&lt;p&gt;Persisting pieces of data is required in many applications. Apple offers solutions to store data for different scenarios. This chapter contains questions and answers on persistence technologies and frameworks such as File System, UserDefaults, SwiftData and Core Data.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is migration in SwiftData, and how does it handle schema changes?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Migration in SwiftData ensures that changes to data models in new app versions do not lead to data loss. SwiftData handles this by transitioning between schema versions through migration plans. Developers can define versioned schemas, specify migration sequences, and choose between lightweight migrations or custom transformations as needed.&lt;/p&gt;&lt;p&gt;This process helps maintain data integrity while evolving the app&amp;#x27;s data structure.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Security&lt;/h3&gt;&lt;p&gt;Many applications need to manage some kind of sensitive user data. To ensure a high level of security, Apple provides different technologies to be used by developers. This chapter provides questions and answers on specific security-related iOS technologies such as keychain or shared web credentials, as well as general cryptography concepts.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What are use cases of a hash function?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Cryptographic hash functions have various security applications, such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Digital Signatures&lt;/strong&gt;: Ensuring the integrity of signed data.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Message Authentication&lt;/strong&gt;: Verifying the authenticity of messages.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Password Verification&lt;/strong&gt;: Protecting stored passwords.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For instance, in password verification, instead of storing plain passwords, the server stores their hash values. When a user logs in, the app hashes the entered password and sends it for comparison. This approach ensures that the original password is neither stored nor transmitted, enhancing security.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Architecture &amp;amp; Design Patterns&lt;/h3&gt;&lt;p&gt;Architectural principles and design patterns are the foundation of robust, maintainable, and scalable iOS applications. This chapter explores key architectural concepts and widely used design patterns in iOS development, providing practical examples and insights into their strengths and weaknesses.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What are the strengths and weaknesses of the MVVM pattern in iOS development?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The MVVM pattern in iOS development separates UI concerns from business logic by introducing a &lt;code&gt;ViewModel&lt;/code&gt; that acts as an intermediary between the &lt;code&gt;Model&lt;/code&gt; and &lt;code&gt;View&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This structure makes code more maintainable, testable, and reusable, as it reduces tight coupling between components. MVVM aligns particularly well with SwiftUI’s declarative nature, where state management and bindings are core features.&lt;/p&gt;&lt;p&gt;However, it can add complexity and require additional boilerplate, particularly in small or simple apps where the benefits may not outweigh the costs. MVVM is most effective in medium-to-large applications with more intricate UI and logic, where clear separation of concerns is crucial.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Chapter: Automated Testing&lt;/h3&gt;&lt;p&gt;Testing is an important part in every software development process. Apple offers tools to write unit, UI and performance tests for iOS applications. This chapter provides questions and answers on general testing concepts and specific iOS testing tools.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What are parameterized tests when working with &lt;code&gt;Swift Testing&lt;/code&gt;?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Swift Testing supports parameterized tests allowing us to run the same test logic multiple times with different input values:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Test(arguments: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;])
func example(input: String) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;h3&gt;Machine Learning&lt;/h3&gt;&lt;p&gt;Machine learning becomes an increasingly important part of software development and engineering jobs. Machine learning techniques are used to build models that can learn patterns in data and make predictions or decisions without being explicitly programmed to do so. These models are used in a variety of applications, including image and speech recognition, natural language processing and recommendation systems. This chapter provides questions and answers on general machine learning concepts and on how to integrate machine learning into an iOS application.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What are some examples of tasks than can be solved with the Natural Language framework out of the box?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The Natural Language framework is a machine learning framework that allows us to process and analyze natural language text. With the Natural Language framework, we can solve a wide range of natural language processing tasks, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;recognizing the language of a text&lt;/li&gt;&lt;li&gt;finding synonyms or similar sentences of a given text&lt;/li&gt;&lt;li&gt;identify and extract named entities from text, such as people, organizations, and locations&lt;/li&gt;&lt;li&gt;part-of-speech tagging, i.e. tagging each word in a piece of text with its part of speech, such as noun, verb, adjective, etc.&lt;/li&gt;&lt;/ul&gt;&lt;hr/&gt;&lt;h3&gt;Working with Legacy Code: UIKit&lt;/h3&gt;&lt;p&gt;With SwiftUI slowly pushing UIKit into the background, UIKit is still part of many existing iOS applications. A deep knowledge of UIKit may be required for an iOS position. This chapter provides questions and answers around key aspects of UIKit.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is the concept behind intrinsic content size of a UIView?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Intrinsic content size represents the amount of space a view needs to display its content. A view can have an intrinsic content height, width or both.&lt;/p&gt;&lt;p&gt;For example, the intrinsic content size of a &lt;code&gt;UILabel&lt;/code&gt; will be the size of the text it contains.&lt;/p&gt;&lt;p&gt;Not all views have an intrinsic content size. When defining a custom view, we are able to decide if it has an intrinsic content size depending on how we setup the view&amp;#x27;s constraints.&lt;/p&gt;&lt;hr/&gt;&lt;h3&gt;Working with Legacy Code: Objective-C&lt;/h3&gt;&lt;p&gt;Objective-C is becoming more and more obsolete after Swift was released in 2014. However, with existing apps written in Objective-C still out there, knowledge on Objective-C may be required for an iOS position. This chapter provides questions and answers on Objective-C basics and the interoperability between Swift and Objective-C.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Example question &amp;amp; answer&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What is an implementation file in Objective-C?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;An implementation file contains the actual implementation for an Objective-C class. It implements all methods defined in the header file and also contains private properties and methods.&lt;/p&gt;&lt;p&gt;The syntax to declare a class implementation looks as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;SomeClass.m&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;#import &amp;quot;SomeClass.h&amp;quot;

@implementation SomeClass
// implement public methods
// define &amp;amp; implement private properties and methods
@end
&lt;/code&gt;&lt;/pre&gt;&lt;hr/&gt;&lt;p&gt;This article is a preview of the book &lt;a href=&quot;/books/&quot;&gt;Preparing for a technical iOS job interview - with over 200 questions &amp;amp; answers&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>book</category><category>technical interview</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding opaque types and protocols with associatedtype in Swift]]></title><description><![CDATA[Learn how to leverage the some keyword for working with protocols with associated types.]]></description><link>https://tanaschita.com/swift-opaque-types-protocols-associatedtype</link><guid isPermaLink="false">https://tanaschita.com/swift-opaque-types-protocols-associatedtype</guid><pubDate>Sun, 05 Jan 2025 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Protocols with &lt;code&gt;associatedtype&lt;/code&gt; or &lt;code&gt;Self&lt;/code&gt; requirements often bring challenges when used as parameters or return types in Swift. Historically, developers needed to rely on generics to work around these limitations, leading to sometimes complex code.&lt;/p&gt;&lt;p&gt;This article will show how to use opaque types to simplify interactions with such protocols.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;As an example, let&amp;#x27;s look at a protocol with an associated type.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol Store {
    associatedtype Item
    func persist(item: Item)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because of the associatedtype, the protocol does not have a fully determined concrete type until we provide a specific implementation. This makes it impossible to use the protocol directly as a parameter or return type without triggering a compile-time error.&lt;/p&gt;&lt;p&gt;For example, writing the following function results in an error:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func cleanup(store: Store) {
    // Error: Protocol &amp;#x27;Store&amp;#x27; can only be used as a generic constraint because it has Self or associated type requirements.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To work around this, we could define the function as a generic:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func cleanup&amp;lt;T: Store&amp;gt;(store: T) {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this works, it introduces complexity. By using opaque types, we can simplify:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func cleanup(store: some Store) {
    // Simplified function declaration.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The opaque declaration is basically syntactic sugar for the equivalent generic code. The compiler will automatically infer the concrete parameter type without needing us to write the generic code.&lt;/p&gt;</content:encoded><category>generics and protocols</category><category>swift</category></item><item><title><![CDATA[Getting started with UI Testing for SwiftUI]]></title><description><![CDATA[Learn how to create user interface tests with the XCTest framework.]]></description><link>https://tanaschita.com/testing-ui-swiftui-xctest-framework</link><guid isPermaLink="false">https://tanaschita.com/testing-ui-swiftui-xctest-framework</guid><pubDate>Sun, 29 Dec 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To ensure that our app&amp;#x27;s user interface behaves correctly during various user actions, iOS provides the XCTest framework for creating UI tests.&lt;/p&gt;&lt;p&gt;In this guide, we&amp;#x27;ll walk through writing UI tests for a SwiftUI application, including how to query UI elements and interact with them.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Introduction to XCTest for UI Testing&lt;/h3&gt;&lt;p&gt;XCTest enables developers to write unit tests, performance tests, and UI tests.&lt;/p&gt;&lt;p&gt;When writing UI tests, we simulate user behavior to verify that the app’s interface functions as expected. UI tests help confirm that specific elements are displayed correctly and respond as intended to user actions.&lt;/p&gt;&lt;p&gt;XCTest provides tools to query the view hierarchy and interact with UI elements programmatically, ensuring reliability in our application’s design and functionality.&lt;/p&gt;&lt;h3&gt;Preparing for UI Testing&lt;/h3&gt;&lt;p&gt;When creating a new SwiftUI application, we can include a UI testing target by selecting the &lt;code&gt;Include Tests&lt;/code&gt; option during project setup. This automatically creates a UI testing target in Xcode.&lt;/p&gt;&lt;p&gt;Alternatively, for existing projects, we can manually add a UI testing target by selecting &lt;code&gt;File &amp;gt; New &amp;gt; Target &amp;gt; UI Testing Bundle&lt;/code&gt; from Xcode’s menu.&lt;/p&gt;&lt;h3&gt;Setting up the Test Class&lt;/h3&gt;&lt;p&gt;To write a UI test, we use a Swift class that extends &lt;code&gt;XCTestCase&lt;/code&gt;. Xcode creates a sample test file when a UI testing target is added. Below is an example of the generated code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import XCTest

class ExampleAppUITests: XCTestCase {

    override func setUpWithError() throws {
        continueAfterFailure = false
    }

    override func tearDownWithError() throws {
    }

    func testExample() throws {
        let app = XCUIApplication()
        app.launch()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Xcode runs every method that starts with the word &lt;code&gt;test&lt;/code&gt; as a separate test. We can already try out and run the &lt;code&gt;testExample&lt;/code&gt; method which simply launches the app.&lt;/p&gt;&lt;p&gt;We can either run all tests by selecting &lt;code&gt;Product &amp;gt; Test&lt;/code&gt; or run a specific test with the play button next to the test method name. If the test target compiles without errors and the &lt;code&gt;testExample&lt;/code&gt; gets a green checkmark, we are ready to go.&lt;/p&gt;&lt;h3&gt;The anatomy of a &lt;code&gt;XCTestCase&lt;/code&gt; test class&lt;/h3&gt;&lt;p&gt;A test class typically contains the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;setUpWithError&lt;/code&gt;: This method is called before each test and is used to set up the initial state.&lt;/li&gt;&lt;li&gt;&lt;code&gt;tearDownWithError&lt;/code&gt;: This method runs after each test and can be used for cleanup.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Here’s an example that configures the test environment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;override func setUpWithError() throws {
    continueAfterFailure = false
    let app = XCUIApplication()
    app.launchArguments = [&amp;quot;isRunningUITests&amp;quot;]
    app.launch()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using launch arguments like &lt;code&gt;&amp;quot;isRunningUITests&amp;quot;&lt;/code&gt;, we can prepare the app for testing by clearing data, mocking responses, or bypassing login flows.&lt;/p&gt;&lt;p&gt;In our app code, we can access arguments with the &lt;code&gt;ProcessInfo&lt;/code&gt; class:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if ProcessInfo.processInfo.arguments.contains(&amp;quot;isRunningUITests&amp;quot;) {
    // Prepare application for UI tests.
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Querying UI elements&lt;/h3&gt;&lt;p&gt;To interact with UI elements, we use the &lt;code&gt;XCUIApplication&lt;/code&gt; class, which provides methods to locate elements in the app’s hierarchy. Elements can be queried by their accessibility identifiers.&lt;/p&gt;&lt;p&gt;For example, to access a button with the identifier &lt;code&gt;loginButton&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let loginButton = app.buttons[&amp;quot;loginButton&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;XCUIElementTypeQueryProvider&lt;/code&gt; provides a lot of different queries such as &lt;code&gt;alerts&lt;/code&gt;, &lt;code&gt;checkBoxes&lt;/code&gt;, &lt;code&gt;collectionViews&lt;/code&gt;, &lt;code&gt;datePickers&lt;/code&gt;, &lt;code&gt;images&lt;/code&gt;, &lt;code&gt;otherElements&lt;/code&gt;, &lt;code&gt;scrollViews&lt;/code&gt;, &lt;code&gt;searchFields&lt;/code&gt;, &lt;code&gt;staticTexts&lt;/code&gt;, &lt;code&gt;toolbarButtons&lt;/code&gt; etc..&lt;/p&gt;&lt;p&gt;SwiftUI views allow us to assign accessibility identifiers using the &lt;code&gt;accessibilityIdentifier&lt;/code&gt; modifier. Here’s an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Login&amp;quot;, action: {
    // Login
})
.accessibilityIdentifier(&amp;quot;loginButton&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Simulating user actions&lt;/h3&gt;&lt;p&gt;XCTest provides various methods to simulate user actions, such as taps, swipes, and text input. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;loginButton.tap()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Other common interaction methods include:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;doubleTap()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;scroll()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;swipeUp()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;swipeRight()&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;pinch(withScale:velocity:)&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;typeText(_:)&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Verifying state in a UI test&lt;/h3&gt;&lt;p&gt;To verify whether an expected element is on the screen, we can use the same macros as in unit testing such as &lt;code&gt;XCTAssert&lt;/code&gt; and &lt;code&gt;XCTAssertTrue&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;XCTAssertTrue(loginButton.exists)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here’s an example for testing a login screen:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testLogin() throws {
    let app = XCUIApplication()
    app.launchArguments = [&amp;quot;isRunningUITests&amp;quot;]
    app.launch()

    let loginButton = app.buttons[&amp;quot;loginButton&amp;quot;]
    XCTAssertTrue(loginButton.waitForExistence(timeout: 2))
    loginButton.tap()

    let usernameField = app.textFields[&amp;quot;usernameInputField&amp;quot;]
    XCTAssertTrue(usernameField.waitForExistence(timeout: 2))
    usernameField.tap()
    usernameField.typeText(&amp;quot;user&amp;quot;)

    let passwordField = app.secureTextFields[&amp;quot;passwordInputField&amp;quot;]
    XCTAssertTrue(passwordField.exists)
    passwordField.tap()
    passwordField.typeText(&amp;quot;password&amp;quot;)

    let submitButton = app.buttons[&amp;quot;submitButton&amp;quot;]
    XCTAssertTrue(submitButton.exists)
    XCTAssertTrue(submitButton.isEnabled)
    submitButton.tap()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;waitForExistence(timeout:)&lt;/code&gt; method assures we don&amp;#x27;t get any test failures due to animations where views might need some time to appear on the screen.&lt;/p&gt;&lt;h3&gt;Recording a UI test&lt;/h3&gt;&lt;p&gt;Xcode provides the possibility to record a UI test.&lt;/p&gt;&lt;p&gt;For that, click inside a UI test method and select &lt;code&gt;Editor &amp;gt; Start recording UI test&lt;/code&gt;. The app will start and generate UI testing code for us as we interact with the app.&lt;/p&gt;&lt;p&gt;The recording might be helpful in some cases and is fun to play around with, but the generated code will need readjustments and improvements in most cases, so it might be more productive to just directly writing it down.&lt;/p&gt;&lt;h3&gt;Accessibility Inspector&lt;/h3&gt;&lt;p&gt;A helpful tool when it comes to UI testing is the Accessibility Inspector. Open it by selecting &lt;code&gt;Xcode &amp;gt; Open Developer Tool &amp;gt; Accessibility Inspector&lt;/code&gt; in Xcode&amp;#x27;s menu.&lt;/p&gt;&lt;p&gt;The Accessibility Inspector shows us different accessibility values when we select a UI element in our app. We can use it to get the accessibility identifiers for the UI tests or to determine which elements require accessibility identifiers.&lt;/p&gt;</content:encoded><category>testing</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Creating a particles snow effect with SpriteKit and SwiftUI]]></title><description><![CDATA[Learn how to create a realistic snow particle effect in SwiftUI using SpriteKit’s particle emitter.]]></description><link>https://tanaschita.com/spritekit-particles-snow-effect-swiftui</link><guid isPermaLink="false">https://tanaschita.com/spritekit-particles-snow-effect-swiftui</guid><pubDate>Sun, 22 Dec 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple’s SpriteKit framework is widely used for building 2D games, but it’s also a great tool for adding visual effects to any iOS app.&lt;/p&gt;&lt;p&gt;In this article, we are going to take a look into SpriteKit&amp;#x27;s particle emitter to create a snow effect and combine it with a SwiftUI view.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Step 1: Adding a SpriteKit particle file&lt;/h3&gt;&lt;p&gt;SpriteKit provides an easy-to-use particle editor to create effects like snow, fire, and smoke. Here’s how you can set up a snow particle file:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;In Xcode, create a new file using &lt;code&gt;File &amp;gt; New &amp;gt; File&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Select &lt;code&gt;Resource &amp;gt; SpriteKit Particle File&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Choose the &lt;em&gt;snow&lt;/em&gt; particle emitter template from the options.&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/4f311d84a4328dd1e7181877853916b4/bf811/xcode-particles-editor.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:58.4%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAClElEQVQoz3WSy08TURSH5x9gBwmP1LRNWqa0lIVopBD+QSISSEyMBlBBE+PKhQtjXEgEqwYEQgttKdNpyzw6nVdpFV+fudfQuNDFl9y7uN/5nXOPcnp8wEmpSKOqUa+eUS4U0SpVLKdLLwzh2zccN8C0fXSjQ9j7BT9+0A1CAqeNZ7dwrRZuy5VnZe9Qo1IoYuo1jJqGWavRONPYzn8in3/P1tYWO+922N75wMs3eV6/zbO7u4t2WsUxTOzmOW27ReD6dDwfpXhU4uN2nr29I4qFCrpusrd7SC43SzQWY3x8nKQgmUAdTzCRUolErvF4fR3XsjktlQk8j8teF9ftoDx/32L1VYW7LwqsvWny7EOHzdcl5nIzRKMxkskkiUSijygwOjrKw9VV2WJTrxN6Pt0wxG75KFpV47hwQqV4jF49w24aHO0fMJvLEYvH/yRMJvukVJWxsTGebmzS80OMRhO35dC2bWzTRDH1OlajgVVvcF7TaTXPKewfkBPC2L8TCqFI2AtCrHMDz3EIPU+KFSG5oqnV5JClcGaGeDxOSk2RSqVQVZWJiTTpdFoKNx8+4nu3108npB0/QDH0Olf8LZy5dYt0OsP169NSlM1OSdnk5CSRSIQnjze4vOjS0HUcy8Y2TCzD+LdQzHBudhZVTfVFmcykLHDjxk2Z9tHaGl0/kDLxKX7b/X/LQjg/Py8fZrNZpqamyGQyTE9PS8Rsn25s8LVzge+0pUxwEQQoZr3BFUIqllW0nElPMDg4yPDwMENDQ4yMjMh7NBplYGCA1fsP+Pn1Esey5GKLdEKqiHU5PT5BK1c4K5WplSscfd7n9so9Fu/cYWVlhcXFRcnS0hLLy8ssLCzwKZ/nS9jBc9p9oWj9N4bswNJUflQYAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s particles editor&quot; title=&quot;Xcode&amp;#x27;s particles editor&quot; src=&quot;/static/4f311d84a4328dd1e7181877853916b4/2e9ed/xcode-particles-editor.png&quot; srcSet=&quot;/static/4f311d84a4328dd1e7181877853916b4/12d18/xcode-particles-editor.png 375w,/static/4f311d84a4328dd1e7181877853916b4/44507/xcode-particles-editor.png 750w,/static/4f311d84a4328dd1e7181877853916b4/2e9ed/xcode-particles-editor.png 1500w,/static/4f311d84a4328dd1e7181877853916b4/bf811/xcode-particles-editor.png 2060w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s particles editor&lt;/div&gt;&lt;p&gt;The built-in particle editor allows us to customize the properties of the particle emitter to achieve the desired snow effect. For example, we can adjust the particle birth rate, lifetime, speed, and texture.&lt;/p&gt;&lt;h3&gt;Step 2: Creating a custom SpriteKit scene&lt;/h3&gt;&lt;p&gt;In SpriteKit, a scene (&lt;code&gt;SKScene&lt;/code&gt;) acts as the root container for all visual content in a 2D space. Think of it as a stage where various elements like sprites, shapes, and particle effects are added as nodes to create a complete visual experience. Each scene can be thought of as a separate screen or a distinct part of your app&amp;#x27;s interface, handling its own rendering and animations.&lt;/p&gt;&lt;p&gt;We&amp;#x27;ll set up a &lt;code&gt;SnowScene&lt;/code&gt; where our snow particle emitter will live, acting as the primary node that holds all the other content for our snowfall effect.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class SnowScene: SKScene {

    let snowEmitterNode = SKEmitterNode(fileNamed: &amp;quot;snow.sks&amp;quot;)

    override func didMove(to view: SKView) {
        guard let snowEmitterNode = snowEmitterNode else { return }
        snowEmitterNode.particleSize = CGSize(width: 50, height: 50)
        snowEmitterNode.particleLifetime = 2
        snowEmitterNode.particleLifetimeRange = 6
        addChild(snowEmitterNode)
    }

    override func didChangeSize(_ oldSize: CGSize) {
        guard let snowEmitterNode = snowEmitterNode else { return }
        snowEmitterNode.particlePosition = CGPoint(x: size.width/2, y: size.height)
        snowEmitterNode.particlePositionRange = CGVector(dx: size.width, dy: size.height)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this code:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;We create an &lt;code&gt;SKEmitterNode&lt;/code&gt; using the particle file and add it to the scene using &lt;code&gt;addChild()&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;The particle properties, such as &lt;code&gt;particleLifetime&lt;/code&gt; and &lt;code&gt;particleLifetimeRange&lt;/code&gt;, are configured to control how long each snow particle stays on the screen.&lt;/li&gt;&lt;li&gt;The &lt;code&gt;didChangeSize(_:)&lt;/code&gt; method is overridden to reposition the snow emitter when the view size changes, ensuring the snow falls across the entire width of the screen.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Step 3: Integrating SpriteKit with SwiftUI&lt;/h3&gt;&lt;p&gt;Now that we have a &lt;code&gt;SnowScene&lt;/code&gt; set up, we can add it to a &lt;code&gt;SwiftUI&lt;/code&gt; view using &lt;code&gt;SpriteView&lt;/code&gt;. This allows us to integrate the particle effects into SwiftUI components.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    var scene: SKScene {
        let scene = SnowScene()
        scene.scaleMode = .resizeFill
        scene.backgroundColor = .clear
        return scene
    }

    var body: some View {
        ZStack {
              Image(&amp;quot;background&amp;quot;)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .ignoresSafeArea()
              SpriteView(scene: scene, options: [.allowsTransparency])
                .ignoresSafeArea()
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;The &lt;code&gt;backgroundColor&lt;/code&gt; of the scene is set to &lt;code&gt;.clear&lt;/code&gt;, allowing the background image to show through.&lt;/li&gt;&lt;li&gt;&lt;code&gt;SpriteView&lt;/code&gt; is configured with the &lt;code&gt;.allowsTransparency&lt;/code&gt; option to render the particles over the SwiftUI view.&lt;/li&gt;&lt;li&gt;The &lt;code&gt;ZStack&lt;/code&gt; ensures the background image and the snow effect are layered correctly.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Step 4: Customizing the snow effect&lt;/h3&gt;&lt;p&gt;You can further enhance the snow effect by experimenting with the properties in the particle editor or modifying them programmatically. Here are a few customization tips:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Particle Size and Lifetime&lt;/strong&gt;: Adjust &lt;code&gt;particleSize&lt;/code&gt; and &lt;code&gt;particleLifetime&lt;/code&gt; to simulate different snowfall conditions, such as light flurries or heavy snow.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Speed and Direction&lt;/strong&gt;: Use &lt;code&gt;particleSpeed&lt;/code&gt; and &lt;code&gt;emissionAngle&lt;/code&gt; to control how fast the snow falls and its direction.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Color and Blend Mode&lt;/strong&gt;: Change &lt;code&gt;particleColor&lt;/code&gt; and &lt;code&gt;blendMode&lt;/code&gt; to achieve unique visual styles, like glowing or shimmering snowflakes.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;With a bit of customization, you can adapt this approach for different particle effects, like rain or confetti, to create a wide range of visual experiences.&lt;/p&gt;</content:encoded><category>spritekit</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to unit test async/await functions in Swift]]></title><description><![CDATA[Learn how to write unit tests for async/await functions.]]></description><link>https://tanaschita.com/testing-swift-async-await</link><guid isPermaLink="false">https://tanaschita.com/testing-swift-async-await</guid><pubDate>Sun, 15 Dec 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, we&amp;#x27;ll cover the basics of unit testing async/await functions, showing how to write test cases for functions that may or may not throw errors.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;With async/await, unit testing asynchronous code in Swift got easier and shorter compared to testing asynchronous code with completion handlers.&lt;/p&gt;&lt;p&gt;We don&amp;#x27;t need to setup and handle an &lt;code&gt;XCTestExpectation&lt;/code&gt; anymore. All we have to do is to mark the test function with &lt;code&gt;async&lt;/code&gt; and call &lt;code&gt;await&lt;/code&gt; on the async call.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import XCTest

class FibonacciSequenceTests: XCTestCase {

    func testFibonacciSequence() async {
        let result = await fibonacciSequence(count: 7)
        XCTAssertEqual([0, 1, 1, 2, 3, 5, 8], result)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this test, the &lt;code&gt;fibonacciSequence(count:)&lt;/code&gt; function is asynchronous, so we simply mark the test function with &lt;code&gt;async&lt;/code&gt; and use &lt;code&gt;await&lt;/code&gt; when calling the function. This eliminates the need for managing expectations or manually waiting for the result.&lt;/p&gt;&lt;p&gt;For async functions that may throw an error, we can handle them using the &lt;code&gt;try&lt;/code&gt; keyword.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testFibonacciSequence() async {
    let result = try? await fibonacciSequence(count: 7)
    XCTAssertNotNil(result)
    XCTAssertEqual([0, 1, 1, 2, 3, 5, 8], result)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternativaly, we can mark our test function with &lt;code&gt;throws&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testFibonacciSequenceWithErrorHandling() async throws {
    let result = try await fibonacciSequence(count: 7)
    XCTAssertEqual(result, [0, 1, 1, 2, 3, 5, 8])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this version, if &lt;code&gt;fibonacciSequence(count:)&lt;/code&gt; throws an error, the test will automatically fail.&lt;/p&gt;&lt;p&gt;If we need to handle specific error cases, we can also use a &lt;code&gt;do-catch&lt;/code&gt; block:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testFibonacciSequenceThrowsError() async throws {
    do {
        let _ = try await fibonacciSequence(count: -1)
        XCTFail(&amp;quot;Expected an error for negative count, but no error was thrown&amp;quot;)
    } catch FibonacciError.invalidCount {
        // Success! The expected error was thrown.
    } catch {
        XCTFail(&amp;quot;Unexpected error thrown: \(error)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>testing</category><category>swift</category></item><item><title><![CDATA[Understanding opaque types in Swift]]></title><description><![CDATA[Explore opaque types in Swift for building flexible and type-safe abstractions.]]></description><link>https://tanaschita.com/swift-opaque-types</link><guid isPermaLink="false">https://tanaschita.com/swift-opaque-types</guid><pubDate>Sun, 08 Dec 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift is a type-safe language, meaning every variable and constant has a defined type that the compiler checks to prevent type-related errors. While this is beneficial, there are times when complex return types make function signatures lengthy and harder to read, especially when working with protocols or generics.&lt;/p&gt;&lt;p&gt;Opaque types offer a powerful solution. By returning an opaque type instead of an explicit type, we can hide the exact underlying type of a value while maintaining type safety.&lt;/p&gt;&lt;p&gt;In this article, we’ll walk through what opaque types are, how to use them, and when they’re most useful in Swift programming.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is an opaque type?&lt;/h3&gt;&lt;p&gt;In Swift, an opaque type is represented by the &lt;code&gt;some&lt;/code&gt; keyword, which we place before a type in a function’s return signature. By using &lt;code&gt;some&lt;/code&gt;, we specify that a function returns a type that conforms to a given protocol without revealing the exact underlying type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func shape() -&amp;gt; some Shape {
    return Circle()
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Practical uses of opaque types&lt;/h3&gt;&lt;h4&gt;Simplifying complex return types&lt;/h4&gt;&lt;p&gt;In SwiftUI, opaque types allow us to hide specific view types. This is beneficial because SwiftUI views often contain complex types that are nested or generic.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func loginView() -&amp;gt; some View {
    VStack {
        Text(...)
        Button(...)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, the function hides the exact type of the view, allowing flexibility and avoiding a complex return type.&lt;/p&gt;&lt;h4&gt;Combining with protocols with associated types&lt;/h4&gt;&lt;p&gt;Protocols with associated types aren’t always usable as standalone types. For instance:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;protocol Container {
    associatedtype Item
    func getItem() -&amp;gt; Item
}

func makeContainer() -&amp;gt; Container {
    // This won’t work, as Container has an associated type.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we cannot just return &lt;code&gt;Container&lt;/code&gt;, since it has an associated type. Instead, we can use an opaque type to specify that the function returns a type conforming to &lt;code&gt;Container&lt;/code&gt;, without needing to specify the associated type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func makeContainer() -&amp;gt; some Container {
    return // Return a concrete type conforming to Container
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Limitations of opaque types&lt;/h3&gt;&lt;p&gt;Opaque types provide great flexibility, but there are some limitations:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Single Concrete Type&lt;/strong&gt;: Opaque types only work when a function returns a single concrete type. We cannot return multiple different types as opaque types within the same function.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;No External Type Information&lt;/strong&gt;: Since the exact type is hidden, the caller cannot make assumptions about the underlying type—only that it conforms to the declared protocol.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For example, if we try to return multiple different types, we’ll get a compiler error:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func shape() -&amp;gt; some Shape {
    if someCondition {
        return Circle()
    } else {
        return Square() // Error: Return type must be the same
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To resolve this, both branches of the function must return the same concrete type.&lt;/p&gt;</content:encoded><category>generics and protocols</category><category>swift</category><category>swiftui</category></item><item><title><![CDATA[Supporting universal links in a SwiftUI application]]></title><description><![CDATA[Learn how to implement universal links to link between content on your iOS app and its connected website.]]></description><link>https://tanaschita.com/ios-universal-links-swiftui</link><guid isPermaLink="false">https://tanaschita.com/ios-universal-links-swiftui</guid><pubDate>Sun, 24 Nov 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Universal Links allow iOS apps to open specific pages in response to standard web URLs. When users tap a universal link, they are redirected to a specific part of an iOS application if installed. If the app is not installed, the link opens in a browser, allowing the connected website to handle it.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore how to set up Universal Links in a SwiftUI application. We’ll go through the configuration steps required on the web and within Xcode, as well as how to handle Universal Link navigation in SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;To support universal links, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Creating and configuring an Apple App Site Association (AASA) file.&lt;/li&gt;&lt;li&gt;Adding the AASA file to the related website.&lt;/li&gt;&lt;li&gt;Adding an associated domains entitlement to the iOS app.&lt;/li&gt;&lt;li&gt;Implement code to handle Universal Links in the app.&lt;/li&gt;&lt;li&gt;Test the universal links implementation.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let’s dive into each step in detail.&lt;/p&gt;&lt;h3&gt;1. Creating and configuring an Apple App Site Association (AASA) file&lt;/h3&gt;&lt;p&gt;The Apple App Site Association file is a file called &lt;code&gt;apple-app-site-association&lt;/code&gt; without an extension in JSON format. Let&amp;#x27;s look at an example for universal links support:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;applinks&amp;quot;:{
      &amp;quot;details&amp;quot;:[
         {
            &amp;quot;appIDs&amp;quot;:[
               &amp;quot;APPIDPREFIX.com.tanaschita.exampleApp&amp;quot;
            ],
            &amp;quot;components&amp;quot;:[
               {
                   &amp;quot;/&amp;quot;: &amp;quot;birds&amp;quot;,
               },
               {
                   &amp;quot;?&amp;quot;: &amp;quot;status&amp;quot;,
               }
            ]
         }
      ]
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this file:&lt;/p&gt;&lt;ol start=&quot;2&quot;&gt;&lt;li&gt;&lt;code&gt;appIDs&lt;/code&gt; specifies which app can handle the links, in the format &lt;code&gt;&amp;lt;YOUR_TEAM_ID&amp;gt;.&amp;lt;bundleID&amp;gt;&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;code&gt;components&lt;/code&gt; specifies URL patterns that the app can handle.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In the example, the app will handle any URL that matches the &lt;code&gt;/birds&lt;/code&gt; path and uses a query with &lt;code&gt;status&lt;/code&gt; as a key, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;https://example.com/birds?status=active
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If no path or pattern is specified, it defaults to &lt;code&gt;*&lt;/code&gt;, matching all URLs under the domain.&lt;/p&gt;&lt;h3&gt;2. Adding the AASA file to the related website&lt;/h3&gt;&lt;p&gt;The apple-app-site-association file should be hosted at:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;https://yourdomain.com/.well-known/apple-app-site-association
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Make sure the file is accessible without any redirects, as iOS will attempt to download it directly from this location.&lt;/p&gt;&lt;h3&gt;3. Adding an associated domains entitlement to the iOS app&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;In Xcode, open your project’s target settings.&lt;/li&gt;&lt;li&gt;Under the &lt;code&gt;Signing &amp;amp; Capabilities&lt;/code&gt; tab, click the &lt;code&gt;+ Capability&lt;/code&gt; button.&lt;/li&gt;&lt;li&gt;Add the Associated Domains capability.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;In the Associated Domains section, we can add the domains configured in the AASA file. The format is &lt;code&gt;applinks:yourdomain.com&lt;/code&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;applinks:example.com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This tells iOS that our app can handle Universal Links for &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; To enable the Associated Domains entitlement, our app must be signed with a provisioning profile that supports this capability. A paid Apple Developer Program membership is required to configure entitlements and provisioning profiles.&lt;/p&gt;&lt;h3&gt;4. Implement code to handle Universal Links in the app.&lt;/h3&gt;&lt;p&gt;SwiftUI provides the &lt;code&gt;onOpenURL&lt;/code&gt; method which is called when a user taps on a universal link our app supports.&lt;/p&gt;&lt;p&gt;Here’s how to set up &lt;code&gt;.onOpenURL&lt;/code&gt; in a SwiftUI view:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ContentView()
.onOpenURL { url in
    // change state based on the provided URL
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;url&lt;/code&gt; parameter provides access to the URL’s components, including &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt;, enabling us to navigate to the appropriate screen within the app based on the link.&lt;/p&gt;&lt;p&gt;To implement routing, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/swiftui-navigationpath/&quot;&gt;how to use NavigationPath for routing in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;5. Testing universal links&lt;/h3&gt;&lt;p&gt;Universal Links only work on a physical device, not the simulator. To test a universal link, we can save it another app, for example in a notes app, and tap on it from there to check if the app opens and routes as planned.&lt;/p&gt;</content:encoded><category>associated domains</category><category>security</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[How to use a Connectable publisher in Combine]]></title><description><![CDATA[Learn how to prevent data loss by using Combine's ConnectablePublisher.]]></description><link>https://tanaschita.com/combine-connectable-publisher</link><guid isPermaLink="false">https://tanaschita.com/combine-connectable-publisher</guid><pubDate>Sun, 17 Nov 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with publishers in Combine, there are situations where we need precise control over when values start emitting. By default, Combine publishers begin emitting as soon as they are created, which can lead to subscribers missing some values if they aren&amp;#x27;t connected in time.&lt;/p&gt;&lt;p&gt;This is where Combine’s &lt;code&gt;ConnectablePublisher&lt;/code&gt; comes in. A &lt;code&gt;ConnectablePublisher&lt;/code&gt; holds off emitting values until its &lt;code&gt;connect()&lt;/code&gt; method is called, giving us more control over when subscribers start receiving values.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s look at an example.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let publisher = Just(5)

subscriber1 = publisher.sink { newValue in
    print(newValue)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.subscriber2 = publisher.sink { newValue in
        print(newValue)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, &lt;code&gt;subscriber2&lt;/code&gt; misses the emitted value &lt;code&gt;5&lt;/code&gt; because the &lt;code&gt;Just&lt;/code&gt; publisher emits it immediately, and &lt;code&gt;subscriber2&lt;/code&gt; subscribes too late.&lt;/p&gt;&lt;h3&gt;Solving the issue with &lt;code&gt;ConnectablePublisher&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;To ensure both subscribers receive the value, we can convert the publisher to a &lt;code&gt;ConnectablePublisher&lt;/code&gt; by calling &lt;code&gt;makeConnectable()&lt;/code&gt; and controlling when it starts emitting values using the &lt;code&gt;connect()&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let publisher = Just(5).makeConnectable()

subscriber1 = publisher.sink { newValue in
    print(newValue)
}

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.subscriber2 = publisher.sink { newValue in
        print(newValue)
    }
    self.connection = publisher.connect()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, the publisher will only emit the value after &lt;code&gt;connect()&lt;/code&gt; is called, ensuring both subscribers receive the value.&lt;/p&gt;&lt;h3&gt;How to automatically connect with first subscriber&lt;/h3&gt;&lt;p&gt;If we want a &lt;code&gt;ConnectablePublisher&lt;/code&gt; to start emitting values right after the first subscriber subscribes, it provides the &lt;code&gt;autoconnect()&lt;/code&gt; method.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;self.timerSubscriber = Timer.publish(every: 5, on: .main, in: .default)
    .autoconnect()
    .sink() { date in
        print (date)
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, the &lt;code&gt;publish&lt;/code&gt; method of &lt;code&gt;Timer&lt;/code&gt; returns a &lt;code&gt;ConnectablePublisher&lt;/code&gt;. By calling &lt;code&gt;autoconnect()&lt;/code&gt; the timer starts sending date values as soon as we subscribe.&lt;/p&gt;</content:encoded><category>combine</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to identify data in Lists and ForEach in SwiftUI]]></title><description><![CDATA[Understanding the Identifiable protocol and \.self in SwiftUI collections.]]></description><link>https://tanaschita.com/swiftui-identifiable</link><guid isPermaLink="false">https://tanaschita.com/swiftui-identifiable</guid><pubDate>Sun, 10 Nov 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In SwiftUI, creating dynamic and responsive views often involves generating content from collections of data using &lt;code&gt;List&lt;/code&gt; and &lt;code&gt;ForEach&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To efficiently manage these dynamic views, SwiftUI needs a reliable way to identify each item in the collection. When the data is uniquely identifiable, SwiftUI can easily track changes, remove items, and insert new views. This is where the &lt;code&gt;Identifiable&lt;/code&gt; protocol and the &lt;code&gt;\.self&lt;/code&gt; key path come into play.&lt;/p&gt;&lt;p&gt;In this article, we’ll explore how to use &lt;code&gt;Identifiable&lt;/code&gt; to provide unique identifiers for custom data types and when to rely on &lt;code&gt;\.self&lt;/code&gt; to identify simple types like strings.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using the &lt;code&gt;Identifiable&lt;/code&gt; protocol for custom types&lt;/h3&gt;&lt;p&gt;For custom data types, the simplest way to give SwiftUI a unique identifier is to make your data model conform to the &lt;code&gt;Identifiable&lt;/code&gt; protocol. The protocol requires a single property &lt;code&gt;id&lt;/code&gt;, which must be unique for each instance of the type.&lt;/p&gt;&lt;p&gt;Here’s an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Book: Identifiable {
    let id = UUID()  // Unique identifier
    let title: String
    let author: String
}

List(books) { book in
    VStack {
        Text(book.title)
        Text(book.author)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, &lt;code&gt;Book&lt;/code&gt; conforms to &lt;code&gt;Identifiable&lt;/code&gt; by providing a unique &lt;code&gt;id&lt;/code&gt; property (a UUID in this case). SwiftUI can now easily track and identify each &lt;code&gt;Book&lt;/code&gt; in the list by referencing the &lt;code&gt;id&lt;/code&gt; property, making the UI updates efficient and reliable.&lt;/p&gt;&lt;h3&gt;Using &lt;code&gt;\.self&lt;/code&gt; for simple data types&lt;/h3&gt;&lt;p&gt;But what if we’re working with a collection of types like &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Int&lt;/code&gt;, or &lt;code&gt;Double&lt;/code&gt;, which don’t inherently conform to &lt;code&gt;Identifiable&lt;/code&gt;? In these cases, we can tell SwiftUI to use the entire value as the identifier by using the &lt;code&gt;\.self&lt;/code&gt; key path.&lt;/p&gt;&lt;p&gt;Here&amp;#x27;s an example using an array of strings:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let fruits = [&amp;quot;Apple&amp;quot;, &amp;quot;Banana&amp;quot;, &amp;quot;Cherry&amp;quot;]

ForEach(fruits, id: \.self) { fruit in
    Text(fruit)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By specifying &lt;code&gt;id: \.self&lt;/code&gt;, we are telling SwiftUI that each &lt;code&gt;String&lt;/code&gt; in the fruits array is unique based on its value. SwiftUI will treat each string as a unique identifier, allowing the framework to correctly manage the views corresponding to each fruit.&lt;/p&gt;&lt;p&gt;Using &lt;code&gt;\.self&lt;/code&gt; works well for simple types, but the values must be unique. If two items in the collection have the same value, SwiftUI will get confused, potentially causing issues.&lt;/p&gt;&lt;p&gt;A reliable way to ensure uniqueness is to wrap each item in a struct that includes a unique id, like a UUID. This approach not only prevents rendering issues but also improves the stability of your SwiftUI views, as each item is uniquely identifiable, even if the values themselves are duplicated. Wrapping simple values also makes it easier to add additional properties in the future if needed.&lt;/p&gt;</content:encoded><category>swiftui</category><category>basics</category><category>ios</category></item><item><title><![CDATA[Dynamic colors in SwiftUI]]></title><description><![CDATA[Learn to use the asset catalog or the programmatic approach to define dynamic colors in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-dynamic-colors</link><guid isPermaLink="false">https://tanaschita.com/swiftui-dynamic-colors</guid><pubDate>Sun, 03 Nov 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dynamic colors allow our iOS app to automatically adapt its color scheme based on user preferences, such as light and dark mode.&lt;/p&gt;&lt;p&gt;In this article, we will explore how to implement dynamic colors in SwiftUI using both asset catalogs and programmatic approaches.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using the asset catalog for dynamic colors&lt;/h3&gt;&lt;p&gt;One way to define dynamic colors in SwiftUI is through the asset catalog. This allows us to specify different colors for light and dark modes without writing any additional code. Here&amp;#x27;s how to do it:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Open the Asset Catalog&lt;/strong&gt; by opening the &lt;code&gt;Assets.xcassets&lt;/code&gt; file in your iOS project.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Create a new color set&lt;/strong&gt; by using the &lt;code&gt;+&lt;/code&gt; button.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Configure the color set&lt;/strong&gt; by opening the &lt;code&gt;Attributes Inspector&lt;/code&gt; and select &lt;code&gt;Any, Dark&lt;/code&gt; in the &lt;code&gt;Appearances&lt;/code&gt; dialog. This will allow us to define colors for both light and dark modes.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Once we&amp;#x27;ve defined the colors, we can use them in our SwiftUI views like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(book.name)
    .foregroundColor(Color(named: &amp;quot;primary&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code uses the color named &amp;quot;primary&amp;quot; from the asset catalog, which automatically adapts to the current user interface style.&lt;/p&gt;&lt;h3&gt;Programmatically defining dynamic colors&lt;/h3&gt;&lt;p&gt;To achieve the same results programmatically, we can make use of the &lt;code&gt;UIColor&lt;/code&gt; type and its dynamic color capabilities. Here’s how we can create a utility function for dynamic colors:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension UIColor {
    static func dynamicColor(light: UIColor, dark: UIColor) -&amp;gt; UIColor {
        return UIColor { traitCollection in
            return traitCollection.userInterfaceStyle == .dark ? dark : light
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then use it to define dynamic colors programmatically:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ColorScheme {

    var primary: Color {
        return color(light: UIColor(red: 0.7, green: 0.1, blue: 0.1, alpha: 1),
                     dark: UIColor(red: 0.9, green: 0.1, blue: 0.1, alpha: 1))
    }

    private func color(light: UIColor, dark: UIColor) -&amp;gt; Color {
        return Color(UIColor.dynamicColor(light: light, dark: dark))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SwiftUI&amp;#x27;s &lt;code&gt;Color&lt;/code&gt; class itself does not provide the trait collection initializer, but as we can see above, we can simply convert the UIKit&amp;#x27;s &lt;code&gt;UIColor&lt;/code&gt; to SwiftUI&amp;#x27;s &lt;code&gt;Color&lt;/code&gt; type.&lt;/p&gt;&lt;p&gt;Now, we can use the created color scheme in our SwiftUI views.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.foregroundColor(colorScheme.primary)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Understanding actors in Swift]]></title><description><![CDATA[Learn how to use Swift actors to prevent data races when working with concurrency.]]></description><link>https://tanaschita.com/swift-concurrency-actors-basics</link><guid isPermaLink="false">https://tanaschita.com/swift-concurrency-actors-basics</guid><pubDate>Sun, 27 Oct 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Concurrency is a complex problem when writing software, especially when dealing with shared mutable state across different threads. With Swift&amp;#x27;s move toward structured concurrency, actors are a powerful feature introduced to make it easier to write safe and efficient concurrent code.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll dive into what actors are, how they differ from other types in Swift, and how they can help you avoid data races in concurrent environments.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;How do Swift actors prevent data races?&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;data race&lt;/strong&gt; occurs when multiple threads access shared mutable data concurrently, and at least one thread modifies the data. This leads to unpredictable results, bugs, and crashes.&lt;/p&gt;&lt;p&gt;Swift&amp;#x27;s &lt;strong&gt;actors&lt;/strong&gt; prevent data races through actor isolation, which ensures:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Only one task can access or modify an actor’s mutable state at any given time.&lt;/li&gt;&lt;li&gt;Synchronous access to an actor&amp;#x27;s mutable state from outside is prohibited.&lt;/li&gt;&lt;li&gt;Direct modification of an actor&amp;#x27;s mutable state from outside is not allowed.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;By using actors, Swift guarantees that shared mutable data is safely managed in a concurrent environment, reducing the risk of data races.&lt;/p&gt;&lt;h3&gt;Defining an actor&lt;/h3&gt;&lt;p&gt;Actors are represented by a reference type called &lt;code&gt;actor&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor Shop {
    let id = &amp;quot;abc&amp;quot;
    var itemsCount = 10

    func purchase() {
        itemsCount-=1
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, &lt;code&gt;Shop&lt;/code&gt; is an actor that holds a mutable state &lt;code&gt;itemsCount&lt;/code&gt;. The actor ensures that only one task can modify &lt;code&gt;itemsCount&lt;/code&gt; at a time. This is the core advantage of actors over classes when it comes to handling concurrent operations.&lt;/p&gt;&lt;h3&gt;Using async/await to access data from an actor&lt;/h3&gt;&lt;p&gt;To interact with an actor&amp;#x27;s properties or methods from outside, we need to use &lt;code&gt;async/await&lt;/code&gt;, marking any potential suspension points with &lt;code&gt;await&lt;/code&gt;. Only immutable data, such as constants (&lt;code&gt;id&lt;/code&gt; in this case), can be accessed synchronously.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let shop = Shop()

print(shop.id) // Synchronous access to immutable property

Task {
    await shop.purchase() // Asynchronous call to modify mutable state
    let count = await shop.itemsCount // Asynchronous access to mutable state
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though we didn&amp;#x27;t explicitly mark purchase as an &lt;code&gt;async&lt;/code&gt; function, we still need to use &lt;code&gt;await&lt;/code&gt; when calling it because it interacts with the actor&amp;#x27;s mutable state. If some other task has already called &lt;code&gt;purchase&lt;/code&gt;, our &lt;code&gt;purchase()&lt;/code&gt; call would suspend and wait for the other one to finish.&lt;/p&gt;&lt;p&gt;This is part of &lt;strong&gt;actor isolation&lt;/strong&gt;, which ensures that only asynchronous access to an actor&amp;#x27;s mutable state is allowed from outside.&lt;/p&gt;&lt;h3&gt;Actor isolation&lt;/h3&gt;&lt;p&gt;Actor isolation is the principle that protects an actor&amp;#x27;s internal state. So when accessing the data of an actor, the following rules apply:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Synchronous access to an actor&amp;#x27;s mutable state from the outside is not allowed.&lt;/li&gt;&lt;li&gt;Asynchronous access via &lt;code&gt;await&lt;/code&gt; is required for mutable state.&lt;/li&gt;&lt;li&gt;Reading immutable properties (constants) from the outside can be done synchronously.&lt;/li&gt;&lt;li&gt;Internal access (within the actor’s methods) can happen synchronously.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These guarantees ensure that concurrent access to shared data is controlled and safe and are known as &lt;em&gt;actor isolation&lt;/em&gt;.&lt;/p&gt;&lt;h3&gt;Actor&amp;#x27;s non-isolated declarations&lt;/h3&gt;&lt;p&gt;There may be cases where we want to allow synchronous access to certain methods or properties of an actor. For this, Swift provides the &lt;code&gt;nonisolated&lt;/code&gt; keyword, which allows a method or property to bypass actor isolation.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor Shop {
    let id = &amp;quot;abc&amp;quot;

    nonisolated func log(message: String) {
        print(&amp;quot;\(id): \(message)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the log method is declared &lt;code&gt;nonisolated&lt;/code&gt;, allowing it to be called synchronously from outside the actor without await. Inside the &lt;code&gt;nonisolated&lt;/code&gt; method, we are only allowed to use nonisolated properties (constants) and methods.&lt;/p&gt;&lt;h3&gt;Handling thread safety with Sendable types&lt;/h3&gt;&lt;p&gt;In Swift, actors are designed to protect their internal state from concurrent access, but we still need to be careful when passing data in and out of an actor. This is where the &lt;code&gt;Sendable&lt;/code&gt; protocol comes into play. &lt;code&gt;Sendable&lt;/code&gt; ensures that any type used in a concurrent environment is thread-safe, meaning it can safely be shared across different tasks without causing data races.&lt;/p&gt;&lt;p&gt;So far, we have only used value types &lt;code&gt;String&lt;/code&gt; or &lt;code&gt;Int&lt;/code&gt; when working with actors. By default, Swift&amp;#x27;s value types like &lt;code&gt;Int&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; are &lt;code&gt;Sendable&lt;/code&gt;, but when working with custom types, it&amp;#x27;s important to ensure that they conform to &lt;code&gt;Sendable&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To check the thread safety of a type we can use the &lt;code&gt;Sendable&lt;/code&gt; protocol:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;actor Shop {
    var owner: Owner
}

struct Owner: Sendable {
    var name: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mutable classes are not thread safe:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;class Owner: Sendable { // compiler error: Non-final class &amp;#x27;Owner&amp;#x27; cannot conform to Sendable
    var name: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since mutable classes are not thread safe we get a compiler error. To avoid this, we should either mark immutable classes as final or prefer using structs, which are value types and inherently thread-safe.&lt;/p&gt;&lt;h3&gt;Actor reentrancy&lt;/h3&gt;&lt;p&gt;One unique feature of Swift actors is reentrancy. When an actor is suspended awaiting an async call, it can process other tasks in its queue. This improves efficiency but can lead to unexpected behavior if we’re not careful, as an actor’s state may change between async calls. Swift automatically manages reentrancy, but it’s important to be aware of this behavior when working with actors.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Extending optionals in Swift]]></title><description><![CDATA[Quick tip on how to extend an optional in Swift.]]></description><link>https://tanaschita.com/swift-extending-optionals</link><guid isPermaLink="false">https://tanaschita.com/swift-extending-optionals</guid><pubDate>Sun, 20 Oct 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Optionals in Swift are a powerful feature that allows us to express the absence of a value. In this article, we&amp;#x27;ll look at how to extend optionals.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Optionals under the hood&lt;/h3&gt;&lt;p&gt;Under the hood, an optional is simply an enumeration with two cases:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Optional&amp;lt;Wrapped&amp;gt; {
    /// The absence of a value.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since an optional is just another type in Swift, it can be extended in the same way we can extend any other type. This allows us to add custom properties or methods to optionals, enhancing their functionality.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;h3&gt;Example: Extending an Optional String&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s say we frequently work with optional &lt;code&gt;String&lt;/code&gt; values and want to check if they are either &lt;code&gt;nil&lt;/code&gt; or empty. We can achieve this by extending the &lt;code&gt;Optional&lt;/code&gt; type specifically when &lt;code&gt;Wrapped&lt;/code&gt; is a &lt;code&gt;String&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension Optional where Wrapped == String {
    var isNilOrEmpty: Bool {
        return self?.isEmpty ?? true
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we are extending an &lt;em&gt;optional&lt;/em&gt; &lt;code&gt;String&lt;/code&gt; by adding &lt;code&gt;where Wrapped == String&lt;/code&gt; to our extension.&lt;/p&gt;&lt;p&gt;Now, we can use the new computed property &lt;code&gt;isNilOrEmpty&lt;/code&gt; on every &lt;em&gt;optional&lt;/em&gt; &lt;code&gt;String&lt;/code&gt; as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var name: String?

if name.isNilOrEmpty {
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swift</category><category>basics</category></item><item><title><![CDATA[How to call async/await functions in SwiftUI]]></title><description><![CDATA[Learn how to integrate async/await functions into your SwiftUI views.]]></description><link>https://tanaschita.com/swiftui-with-async-await</link><guid isPermaLink="false">https://tanaschita.com/swiftui-with-async-await</guid><pubDate>Sun, 13 Oct 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, we’ll explore different approaches to calling async/await functions in SwiftUI using tasks and view modifiers. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Depending on the situation, we can call asynchronous functions in SwiftUI at various points, such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;When a SwiftUI view appears on the screen.&lt;/li&gt;&lt;li&gt;In response to a user interaction like a button tap.&lt;/li&gt;&lt;li&gt;When performing gestures, like pulling to refresh content.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s look at each scenario.&lt;/p&gt;&lt;h3&gt;Calling async functions when a SwiftUI view appears&lt;/h3&gt;&lt;p&gt;When we need to trigger an asynchronous operation as soon as a SwiftUI view appears, the &lt;code&gt;task&lt;/code&gt; modifier is ideal. This modifier allows us to create a task when the view is presented and automatically cancels it if the view disappears.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    Text(viewModel.status)
        .task {
            await viewModel.refreshStatus()
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the task starts immediately when the &lt;code&gt;Text&lt;/code&gt; view appears. SwiftUI ensures that if the view is removed, the task is canceled.&lt;/p&gt;&lt;h3&gt;Calling async functions in response to user actions&lt;/h3&gt;&lt;p&gt;In situations where an async task is triggered by a user action (like pressing a button), we can leverage the &lt;code&gt;Task&lt;/code&gt; type to perform the asynchronous work within the button’s action handler.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;refresh status&amp;quot;) {
    Task {
        await viewModel.refreshStatus()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, when the user taps the button, a new task is created, and the &lt;code&gt;refreshStatus()&lt;/code&gt; async function is called. This approach keeps the button&amp;#x27;s action handler non-blocking, allowing the app to remain responsive during the async work.&lt;/p&gt;&lt;h3&gt;Using the &lt;code&gt;refreshable&lt;/code&gt; modifier for pull-to-refresh&lt;/h3&gt;&lt;p&gt;SwiftUI also provides the &lt;code&gt;refreshable&lt;/code&gt; modifier, which simplifies handling the common pull-to-refresh gesture.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;List {
    // List items
}
.refreshable {
    await viewModel.refreshMessages()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This modifier takes an async closure and runs it when the user performs the refresh gesture.&lt;/p&gt;</content:encoded><category>async/await</category><category>swiftui</category><category>concurrency</category></item><item><title><![CDATA[Understanding localization with LocalizedStringKey in SwiftUI]]></title><description><![CDATA[Learn the advantages and limits of LocalizedStringKey in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-localization</link><guid isPermaLink="false">https://tanaschita.com/swiftui-localization</guid><pubDate>Sun, 06 Oct 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Working with localized strings in SwiftUI can sometimes be confusing due to the variety of available approaches using &lt;code&gt;LocalizedStringKey&lt;/code&gt; or &lt;code&gt;NSLocalizedString&lt;/code&gt;. Here are some examples of valid ways to use localized strings in SwiftUI:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello.world&amp;quot;)
Text(LocalizedStringKey(&amp;quot;hello.world&amp;quot;))
Text(NSLocalizedString(&amp;quot;hello.world&amp;quot;, comment: &amp;quot;&amp;quot;))
Text(String(format: NSLocalizedString(&amp;quot;hello&amp;quot;, comment: &amp;quot;&amp;quot;), name))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s dive deeper into these approaches and understand how they work and what their differences are.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;SwiftUI&amp;#x27;s LocalizedStringKey type&lt;/h3&gt;&lt;p&gt;&lt;code&gt;LocalizedStringKey&lt;/code&gt; is a type that SwiftUI uses to handle localized strings. It interprets keys from the app&amp;#x27;s &lt;code&gt;.strings&lt;/code&gt; or &lt;code&gt;.xcstrings&lt;/code&gt; files and replaces them with their localized equivalents at runtime.&lt;/p&gt;&lt;p&gt;When we pass a string literal into a SwiftUI view like &lt;code&gt;Text&lt;/code&gt; or &lt;code&gt;Button&lt;/code&gt;, SwiftUI automatically converts it into a &lt;code&gt;LocalizedStringKey&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello.world&amp;quot;) // Interpreted as LocalizedStringKey at compile-time
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While SwiftUI infers &lt;code&gt;LocalizedStringKey&lt;/code&gt; from string literals, we can also explicitly use the type in code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(LocalizedStringKey(&amp;quot;hello.world&amp;quot;)) // Explicitly using LocalizedStringKey
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This can be helpful when passing strings dynamically, as SwiftUI only treats &lt;strong&gt;string literals&lt;/strong&gt; as &lt;code&gt;LocalizedStringKey&lt;/code&gt;. For instance, if we pass a string variable to a &lt;code&gt;Text&lt;/code&gt; view, SwiftUI will not interpret it as a key, but as plain text.&lt;/p&gt;&lt;p&gt;One significant limitation of &lt;code&gt;LocalizedStringKey&lt;/code&gt; in SwiftUI is its inability to handle formatted strings that include dynamic content. For instance, consider the localized key pair:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;&amp;quot;hello&amp;quot; = &amp;quot;Hello %@&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, we must revert to using &lt;code&gt;NSLocalizedString&lt;/code&gt;, which allows for proper string formatting and parameterization.&lt;/p&gt;&lt;h3&gt;Using NSLocalizedString&lt;/h3&gt;&lt;p&gt;If we need more control over localization such as runtime flexibility or string formatting, we can use the Foundation API &lt;code&gt;NSLocalizedString&lt;/code&gt;. This function retrieves a localized &lt;code&gt;String&lt;/code&gt; from the app&amp;#x27;s localization files.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(NSLocalizedString(&amp;quot;hello.world&amp;quot;, comment: &amp;quot;&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For strings that require parameters or formatting, &lt;code&gt;NSLocalizedString&lt;/code&gt; works well:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(String(format: NSLocalizedString(&amp;quot;hello&amp;quot;, comment: &amp;quot;&amp;quot;), name))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To streamline localization with &lt;code&gt;NSLocalizedString&lt;/code&gt;, we can add an extension to the String type:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension String {
    var localized: String {
        return NSLocalizedString(self, comment: &amp;quot;&amp;quot;)
    }

    func localized(_ args: CVarArg...) -&amp;gt; String {
        let localizedString = NSLocalizedString(self, comment: &amp;quot;&amp;quot;)
        return String(format: localizedString, arguments: args)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this extension, we can simplify our localized string handling as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;hello.world&amp;quot;.localized)
Text(&amp;quot;hello&amp;quot;.localized(name))
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>localization</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Getting started with async/await in Swift]]></title><description><![CDATA[Learn how use async/await for asynchronous programming.]]></description><link>https://tanaschita.com/swift-async-await</link><guid isPermaLink="false">https://tanaschita.com/swift-async-await</guid><pubDate>Sun, 29 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s &lt;code&gt;async/await&lt;/code&gt; syntax provides a clean and efficient way to handle asynchronous tasks. The async/await syntax is designed to make asynchronous code look and behave more like synchronous code. It allows us to write code that is both easy to understand and free of so-called callback hell.&lt;/p&gt;&lt;p&gt;In this guide, we’ll explore the fundamentals of async/await, see how it compares to traditional callback-based methods, and walk through practical examples.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The callback solution&lt;/h3&gt;&lt;p&gt;Before async/await, handling asynchronous tasks often involved using completion handlers. This method requires nesting callbacks, which can quickly become unwieldy:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetchUserData(userId: String, completion: @escaping (Result&amp;lt;User, Error&amp;gt;) -&amp;gt; Void) {
    networkRequest(for: userId) { result in
        switch result {
        case .success(let data):
            parseUserData(data) { parseResult in
                switch parseResult {
                case .success(let user):
                    completion(.success(user))
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        case .failure(let error):
            completion(.failure(error))
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach, while functional, can lead to deeply nested code and make error handling more complex. Async/await provides a cleaner and more readable alternative.&lt;/p&gt;&lt;h3&gt;The async/await solution&lt;/h3&gt;&lt;p&gt;Using async/await, the same functionality can be implemented with significantly less code:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func fetchUserData(userId: String) async throws -&amp;gt; User {
    let data = try await networkRequest(for: userId)
    let user = try await parseUserData(data)
    return user
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With async/await, the nested callbacks are replaced by a straightforward sequence of await calls. It also improved safety, as it is now guaranteed to return a result, whereas a completion handler is not guaranteed to be called.&lt;/p&gt;&lt;h3&gt;The async/await syntax&lt;/h3&gt;&lt;p&gt;Here&amp;#x27;s a breakdown of the async/await syntax:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Declaring async functions&lt;/strong&gt;: Use the &lt;code&gt;async&lt;/code&gt; keyword to declare a function that performs asynchronous work.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetchData() async -&amp;gt; Data {
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Handling errors&lt;/strong&gt;: If the function can throw errors, add the &lt;code&gt;throws&lt;/code&gt; keyword.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fetchData() async throws -&amp;gt; Data {
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Calling async functions:&lt;/strong&gt; Use &lt;code&gt;await&lt;/code&gt; to call asynchronous functions and wait for their results.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let data = await fetchData()
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Calling async throwing functions:&lt;/strong&gt; Use &lt;code&gt;await&lt;/code&gt; with do catch blocks.&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;do {
    let data = try await fetchData()
} catch let error {
    print(error)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Leveraging built-in async functions&lt;/h3&gt;&lt;p&gt;Many of Swift’s standard library functions support async/await. For example &lt;code&gt;URLSession&lt;/code&gt; comes with async functions for network requests:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let (data, response) = try await URLSession.shared.data(from: imageURL)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Creating async functions with continuations&lt;/h3&gt;&lt;p&gt;For integrating with APIs that use completion handlers, Swift provides continuations to bridge the gap:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func fibonacciSequence(count: Int) async -&amp;gt; [Int] {

    return await withCheckedContinuation({ continuation in
        fibonacciSequence(count: count) { result in
            continuation.resume(returning: result)
        }
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the above example, we use an existing completion handler function to wrap it in an async function. As soon as we call &lt;code&gt;withCheckedContinuation(function:_:)&lt;/code&gt;, the function suspends.&lt;/p&gt;&lt;p&gt;Continuations must be resumed exactly once on every path. The compiler produces an error if we forget to resume or call resume multiple times.&lt;/p&gt;&lt;p&gt;If the function needs to throw an error, we can use &lt;code&gt;withCheckedThrowingContinuation(function:_:)&lt;/code&gt; instead.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding Substring and String indices in Swift]]></title><description><![CDATA[Learn how to use Swift's String type and its companions Character, Index and Substring.]]></description><link>https://tanaschita.com/swift-strings-indices-and-substrings</link><guid isPermaLink="false">https://tanaschita.com/swift-strings-indices-and-substrings</guid><pubDate>Sun, 22 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Representing text values, Strings are one of the most common data structures we use when coding. In Swift, working with strings can be more complex than it first appears due to its unique handling of indices and substrings.&lt;/p&gt;&lt;p&gt;In this article, we will learn how to work with Swift&amp;#x27;s &lt;code&gt;String&lt;/code&gt; type and its companions &lt;code&gt;Character&lt;/code&gt;, &lt;code&gt;Index&lt;/code&gt; and &lt;code&gt;Substring&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Swift&amp;#x27;s &lt;code&gt;String&lt;/code&gt; type is essentially a collection of characters represented by the &lt;code&gt;Character&lt;/code&gt; type. Like other collections, strings can be iterated over:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;for character in &amp;quot;hello 🐌&amp;quot; {
      print(character) // prints &amp;quot;h&amp;quot;, &amp;quot;e&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;l&amp;quot;, &amp;quot;o&amp;quot;, &amp;quot;🐌&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every &lt;code&gt;Character&lt;/code&gt; represents a human-readable character which can include multiple Unicode characters. Unicode is a standard for encoding text, allowing representation of nearly any character from any language.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let emoji: String = &amp;quot;🐌&amp;quot;
emoji.count // returns 1
emoji.utf8.count // returns 4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example shows that the number of Swift characters and UTF-8 bytes may differ.&lt;/p&gt;&lt;h3&gt;String indices &amp;amp; substrings&lt;/h3&gt;&lt;p&gt;Unlike languages that allow integer-based indexing such as &lt;code&gt;string[0]&lt;/code&gt;, Swift requires us to use &lt;code&gt;String.Index&lt;/code&gt; to work with positions in a string. This is due to Swift’s Unicode-compliant string handling, where characters might consist of multiple Unicode code points and bytes.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;String.Index&lt;/code&gt; type represents the position of each &lt;code&gt;Character&lt;/code&gt; in a &lt;code&gt;String&lt;/code&gt;. Each string comes with a &lt;code&gt;startIndex&lt;/code&gt; and an &lt;code&gt;endIndex&lt;/code&gt;. Using those we can derive any other index as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let hello = &amp;quot;hello world&amp;quot;
let index = hello.index(hello.startIndex, offsetBy: 6) // index with an offset of 6 characters

hello[index] // returns Character w
hello.prefix(upTo: index) // returns Substring &amp;quot;hello &amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above &lt;code&gt;prefix(upTo:)&lt;/code&gt; method returns a &lt;code&gt;Substring&lt;/code&gt; and not a &lt;code&gt;String&lt;/code&gt;. This is because substrings share memory with the original string, making them more efficient.&lt;/p&gt;&lt;p&gt;To simplify index usage, we can create extensions to abstract implementation details. For example, here’s an extension for the &lt;code&gt;prefix&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension String {
    func prefix(upTo: Int) -&amp;gt; String {
        let toIndex = self.index(startIndex, offsetBy: upTo)
        return String(self[..&amp;lt;toIndex])
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The extension allows us to directly use an &lt;code&gt;Int&lt;/code&gt; type instead of &lt;code&gt;Index&lt;/code&gt;. We can now call &lt;code&gt;hello.prefix(upTo: 5)&lt;/code&gt; to get a prefix of the string.&lt;/p&gt;</content:encoded><category>swift</category><category>basics</category></item><item><title><![CDATA[How to set up and use Core Data with SwiftUI]]></title><description><![CDATA[Learn the basics for using the Core Data framework with SwiftUI in iOS.]]></description><link>https://tanaschita.com/coredata-with-swiftui</link><guid isPermaLink="false">https://tanaschita.com/coredata-with-swiftui</guid><pubDate>Sun, 15 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With SwiftData, Apple introduced a new state-of-the-art persistence framework for iOS. However, Core Data remains a powerful and essential framework for managing complex data structures in many iOS projects.&lt;/p&gt;&lt;p&gt;This guide will walk you through the fundamentals of integrating Core Data with SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The Core Data Stack&lt;/h3&gt;&lt;p&gt;Before we jump into the example, let&amp;#x27;s recap the most important aspects about Core Data. The Core Data stack consists of:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;NSManagedObjectModel&lt;/code&gt;: Represents our model objects, referred to as entities.&lt;/li&gt;&lt;li&gt;&lt;code&gt;NSPersistentStoreCoordinator&lt;/code&gt;: Manages the underlying database.&lt;/li&gt;&lt;li&gt;&lt;code&gt;NSManagedObjectContext&lt;/code&gt;: Allows us to create, read, update, and delete entities.&lt;/li&gt;&lt;li&gt;&lt;code&gt;NSPersistentContainer&lt;/code&gt;: Provides a way to set up the entire Core Data stack.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Basic Core Data Setup with SwiftUI&lt;/h3&gt;&lt;p&gt;The easiest way to set up Core Data with SwiftUI is by creating a new project and selecting the &lt;code&gt;Core Data&lt;/code&gt; option as the storage method in the project creation dialog.&lt;/p&gt;&lt;p&gt;After doing this, Xcode automatically generates a basic Core Data configuration for us. This setup includes using the &lt;code&gt;NSPersistentContainer&lt;/code&gt; to initialize the Core Data stack and inject it into the root SwiftUI view, as shown below:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct ExampleApp: App {
    let persistenceController = PersistenceController.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code injects the &lt;code&gt;managedObjectContext&lt;/code&gt; into the &lt;code&gt;ContentView&lt;/code&gt;, allowing the view to interact with the Core Data stack:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example project also includes an &lt;code&gt;AppName.xcdatamodeld&lt;/code&gt; file where we define our data model. This file is where we create and manage our data objects, their properties, and relationships.&lt;/p&gt;&lt;h3&gt;Fetching data&lt;/h3&gt;&lt;p&gt;To request items from the Core Data store, SwiftUI provides a &lt;code&gt;FetchRequest&lt;/code&gt; property wrapper.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults&amp;lt;Item&amp;gt;

    var body: some View {
        List {
            ForEach(items) { item in
                Text(&amp;quot;Item at \(item.timestamp, formatter: itemFormatter)&amp;quot;)
            }
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, &lt;code&gt;ContentView&lt;/code&gt; has an &lt;code&gt;items&lt;/code&gt; property that holds the results of the &lt;code&gt;FetchRequest&lt;/code&gt;. The &lt;code&gt;List&lt;/code&gt; view is automatically updated whenever an item is added, deleted, or modified.&lt;/p&gt;&lt;p&gt;For a deeper dive into fetch requests, check out &lt;a href=&quot;/swiftui-fetchrequest-property-wrapper/&quot; target=&quot;\_blank&quot;&gt;@FetchRequest property wrapper in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Adding new items&lt;/h3&gt;&lt;p&gt;To add new items to the Core Data store, we can also use the managed object context:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private func addItem() {
    let newItem = Item(context: viewContext)
    newItem.timestamp = Date()

    do {
        try viewContext.save()
    } catch {
        // Error handling
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It initializes a new &lt;code&gt;Item&lt;/code&gt; entity, sets its attributes and calls &lt;code&gt;save()&lt;/code&gt; on the managed object context. The &lt;code&gt;addItem&lt;/code&gt; can be called by any SwiftUI view, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(action: addItem) {
    Label(&amp;quot;Add Item&amp;quot;, systemImage: &amp;quot;plus&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Deleting items&lt;/h3&gt;&lt;p&gt;Deleting items works similar to adding items. Here is an example of using the &lt;code&gt;onDelete&lt;/code&gt; modifier of a SwiftUI &lt;code&gt;List&lt;/code&gt; view:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;List {
    ForEach(items) { item in
        Text(&amp;quot;Item at \(item.timestamp!, formatter: itemFormatter)&amp;quot;)
    }
    .onDelete(perform: deleteItems)
}

private func deleteItems(offsets: IndexSet) {
    let itemsToDelete = offsets.map { items[$0] }
    itemsToDelete.forEach { viewContext.delete($0) }

    do {
        try viewContext.save()
    } catch {
        // handle error
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is called whenever an item is swipe-deleted from the &lt;code&gt;List&lt;/code&gt; view. SwiftUI provides a set of indices to the closure, which are used to delete the corresponding items.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Introduction to supporting VoiceOver in SwiftUI]]></title><description><![CDATA[Learn how to use different accessibility modifiers in SwiftUI.]]></description><link>https://tanaschita.com/ios-accessibility-voiceover-swiftui-guide</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-voiceover-swiftui-guide</guid><pubDate>Sun, 08 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Making an app accessible is crucial for ensuring that all users, including those with disabilities, can enjoy a seamless experience. For instance, users who rely on assistive technologies like VoiceOver should be able to interact with our apps without needing to see the screen.&lt;/p&gt;&lt;p&gt;SwiftUI simplifies the process of adding accessibility to our apps by automatically setting accessibility labels and values for standard views like navigation views, lists, text fields, sliders, and buttons. However, there are scenarios where additional work is needed to enhance the user experience, particularly when using custom views, images, or image-only buttons.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive into a practical example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Finding VoiceOver issues&lt;/h3&gt;&lt;p&gt;The first step in improving accessibility is to identify potential VoiceOver issues. We can do this by either activating VoiceOver on our device or by using Xcode’s Accessibility Inspector.&lt;/p&gt;&lt;p&gt;To open the Accessibility Inspector, select &lt;code&gt;Xcode &amp;gt; Open Developer Tool &amp;gt; Accessibility Inspector&lt;/code&gt; in Xcode&amp;#x27;s menu.&lt;/p&gt;&lt;p&gt;The Accessibility Inspector allows us to navigate through different view elements and listen to how VoiceOver reads them. This helps us assess whether the accessibility values are providing a good user experience.&lt;/p&gt;&lt;p&gt;While the Accessibility Inspector is a useful development tool, it doesn’t replace the need to test accessibility features with VoiceOver on an actual device. To learn how to use VoiceOver, check out this &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-voiceover-gestures/&quot;&gt;guide on VoiceOver gestures for iOS&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Example: Improving accessibility of an image-only button&lt;/h3&gt;&lt;p&gt;When using buttons with titles, SwiftUI automatically assigns appropriate accessibility values.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Cancel&amp;quot;, action: {
    // Cancel button action
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, VoiceOver will correctly read &lt;em&gt;Cancel Button&lt;/em&gt; to the user, requiring no further action on our part.&lt;/p&gt;&lt;p&gt;However, the situation changes when using image-only buttons. SwiftUI will still recognize the element as a button, but without an appropriate label, VoiceOver will only announce &lt;em&gt;Button&lt;/em&gt; which is unhelpful for users who cannot see the image.&lt;/p&gt;&lt;p&gt;To address this, we can use the &lt;code&gt;accessibilityLabel(_:)&lt;/code&gt; modifier to provide a descriptive label for the button:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;&amp;quot;, action: {
    // Favorites button action
})
.buttonStyle(ImageButtonStyle(systemImage: &amp;quot;heart&amp;quot;))
.accessibilityLabel(&amp;quot;Favorites&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this label in place, VoiceOver will now read &lt;em&gt;Favorites Button&lt;/em&gt; offering much clearer guidance to the user.&lt;/p&gt;&lt;p&gt;Besides &lt;code&gt;accessibilityLabel(_:)&lt;/code&gt;, SwiftUI provides other accessibility modifiers to improve accessibility.&lt;/p&gt;&lt;h3&gt;Example: Improving accessibility when using &lt;code&gt;onTapGesture&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;When using &lt;code&gt;onTapGesture&lt;/code&gt; in SwiftUI, the view doesn&amp;#x27;t automatically gain accessibility traits, which can make it invisible to VoiceOver users. To ensure accessibility, we need to add appropriate labels and traits.&lt;/p&gt;&lt;p&gt;Consider an example where an image triggers an action when tapped:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Image(systemName: &amp;quot;heart&amp;quot;)
    .onTapGesture {
        favoriteItem()
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In its current form, VoiceOver users won&amp;#x27;t know this image is interactive. We can improve accessibility by adding a label and traits:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Image(systemName: &amp;quot;star&amp;quot;)
    .onTapGesture {
        favoriteItem()
    }
    .accessibilityLabel(&amp;quot;Favorite&amp;quot;)
    .accessibilityAddTraits(.isButton)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Example: Improving accessibility by combining views to one accessibility element&lt;/h3&gt;&lt;p&gt;Sometimes, multiple views on the screen represent a single piece of information or an action. For example, a label and an icon together might indicate the same function. By default, VoiceOver will read each view separately, which can be confusing. To improve accessibility, we can combine these views into one accessibility element.&lt;/p&gt;&lt;p&gt;Consider this example with a text label and an image:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;HStack {
    Image(systemName: &amp;quot;heart&amp;quot;)
    Text(&amp;quot;Favorite&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, VoiceOver will read &amp;quot;Star&amp;quot; and &amp;quot;Favorite&amp;quot; separately. We can improve this by combining them into a single accessible element:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;HStack {
    Image(systemName: &amp;quot;star&amp;quot;)
    Text(&amp;quot;Favorite&amp;quot;)
}
.accessibilityElement(children: .combine)
.accessibilityLabel(&amp;quot;Favorite&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Commonly used accessibility modifiers&lt;/h3&gt;&lt;p&gt;To summarize, here is a list of the commonly used accessibility modifiers:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;accessibilityLabel(_:)&lt;/code&gt; to describe the contents of a view.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityValue(_:)&lt;/code&gt; to describe the value that the view contains, if it is different from the view&amp;#x27;s accessibility label.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityHint(_:)&lt;/code&gt; to describe what happens after performing the view’s action.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityAction(_:_:)&lt;/code&gt; to add a custom accessibility action to the view.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityAddTraits(_:)&lt;/code&gt; to add a trait like &lt;code&gt;.isButton&lt;/code&gt; or &lt;code&gt;.isSearchField&lt;/code&gt; to the view.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityRemoveTraits(_:)&lt;/code&gt; to remove a trait from the view.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityHidden(_:)&lt;/code&gt; to specify whether to hide the view from accessibility features.&lt;/li&gt;&lt;li&gt;&lt;code&gt;accessibilityElement(children:)&lt;/code&gt; to create a new accessibility element and influence its child views behaviour, for example to combine multiple views into one accessibility element.&lt;/li&gt;&lt;/ul&gt;</content:encoded><category>accessibility</category><category>swiftui</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Cheat sheet on VoiceOver gestures for iOS]]></title><description><![CDATA[Learn how to test accessibility using VoiceOver for iOS.]]></description><link>https://tanaschita.com/ios-accessibility-voiceover-gestures</link><guid isPermaLink="false">https://tanaschita.com/ios-accessibility-voiceover-gestures</guid><pubDate>Sat, 07 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As we established in this &lt;a target=&quot;_blank&quot; href=&quot;/ios-accessibility-voiceover-swiftui-guide/&quot;&gt;VoiceOver guide for SwiftUI&lt;/a&gt;, the Accessibility Inspector is really helpful during development, but doesn&amp;#x27;t replace testing accessibility with VoiceOver on a real device.&lt;/p&gt;&lt;p&gt;When VoiceOver is enabled, standard gestures behave differently, and additional gestures allow us to navigate the app and interact with elements.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Activating the VoiceOver shortcut&lt;/h3&gt;&lt;p&gt;We can activate VoiceOver through &lt;code&gt;Settings &amp;gt; Accessibility &amp;gt; VoiceOver&lt;/code&gt;. To be able to quickly toggle VoiceOver on and off, we can set up a shortcut on our iOS device by navigating to &lt;code&gt;Settings &amp;gt; Accessibility &amp;gt; Accessibility Shortcut &amp;gt; VoiceOver&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;To be able to quickly switch VoiceOver on and off, we can activate a shortcut on the iOS device by selecting &lt;code&gt;Settings &amp;gt; Accessibility &amp;gt; Accessibility Shortcut &amp;gt; VoiceOver&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Now, we can triple-click the side button to switch VoiceOver on and off. This is especially handy when we need to test specific parts of the app without keeping VoiceOver on constantly.&lt;/p&gt;&lt;h3&gt;Frequently used VoiceOver gestures&lt;/h3&gt;&lt;p&gt;Once VoiceOver is activated, the following gestures are helpfull to navigate and interact with the app:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Drag the finger over the screen&lt;/strong&gt; to explore items. VoiceOver will announce each touched item.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Tap or touch an item&lt;/strong&gt; to select it and let VoiceOver name it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Double tap&lt;/strong&gt; to activate a selected item.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Swipe right or left&lt;/strong&gt; to select the next or previous item.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Two-finger tap&lt;/strong&gt; to pause or resume speech.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Two-finger swipe up or down&lt;/strong&gt; to let VoiceOver read the entire screen from the top or from the selected item.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Three-finger swipe down, up, right or left&lt;/strong&gt; to scroll.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These gestures are sufficient for testing most app features. For more advanced interactions, refer to &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://support.apple.com/en-au/guide/iphone/iph3e2e2281/ios&quot;&gt;Apple&amp;#x27;s official VoiceOver gestures guide&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>accessibility</category><category>ios</category></item><item><title><![CDATA[Swift optionals cheat sheet]]></title><description><![CDATA[Get an overview on Swift optionals, including optional binding, forced unwrapping, implicit optionals, nil coalescing operator and more.]]></description><link>https://tanaschita.com/swift-optionals-cheat-sheet</link><guid isPermaLink="false">https://tanaschita.com/swift-optionals-cheat-sheet</guid><pubDate>Sun, 01 Sep 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s optionals are a powerful and fundamental feature that allows developers to write expressive, safe code by handling the absence of values gracefully. Understanding how to work with optionals — especially concepts like forced unwrapping, optional binding, and implicit optionals — is essential when developing with Swift.&lt;/p&gt;&lt;p&gt;This cheat sheet covers different scenarios about Swift optionals, including optional binding, forced unwrapping, nil coalescing and more.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;1. Declaring Optionals&lt;/h3&gt;&lt;p&gt;An Optional can hold either a value or nil. Optionals are declared using a question mark &lt;code&gt;?&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var name: String? = &amp;quot;Lea&amp;quot;
var age: Int? = nil
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Forced Unwrapping&lt;/h3&gt;&lt;p&gt;When using an exclamation mark &lt;code&gt;!&lt;/code&gt;, we can forcibly unwrap an optional. Forced unwrapping leads to runtime crashes if the value is &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let name = user.name! // Crashes if user.name is nil
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. Optional Binding&lt;/h3&gt;&lt;p&gt;Optional binding safely unwraps an optional. If the optional contains a value, it is assigned to a new constant or variable.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let name = user.name {
    print(name)
} else {
    print(&amp;quot;optionalString is nil&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When unwrapping an optional we can also use a concise version without declaring a new variable:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var greeting: String?

if let greeting {
    print(greeting)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For multiple optionals:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if let name = user.name, let age = user.age {
    print(&amp;quot;Both have values&amp;quot;)
} else {
    print(&amp;quot;One or both are nil&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. Guard Statement&lt;/h3&gt;&lt;p&gt;&lt;code&gt;guard&lt;/code&gt; is used to unwrap optionals, ensuring that a value is present before continuing. It&amp;#x27;s particularly useful in functions.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func greet(user: User) {
    guard let name = user.name else { return }
    // Use name
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5. Nil Coalescing Operator&lt;/h3&gt;&lt;p&gt;The nil coalescing operator &lt;code&gt;??&lt;/code&gt; provides a default value if the optional is nil.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let note = input ?? &amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;6. Implicitly Unwrapped Optionals&lt;/h3&gt;&lt;p&gt;Implicitly unwrapped optionals automatically unwrap when accessed but will crash if nil. They are declared using an exclamation mark ! after the type.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var greeting: String! = &amp;quot;Hello&amp;quot;
print(greeting) // No need to unwrap, but will crash if nil
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;7. Optional Chaining&lt;/h3&gt;&lt;p&gt;Optional chaining allows us to call properties, methods, and subscripts on an optional that might be nil. The whole chain fails gracefully if any link is nil.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let street = user?.address?.street // Returns an optional String, nil if user or address is nil
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;9. Comparing Optionals&lt;/h3&gt;&lt;p&gt;Optionals can be compared using &lt;code&gt;==&lt;/code&gt; and &lt;code&gt;!=&lt;/code&gt;. Two optionals are equal if both are &lt;code&gt;nil&lt;/code&gt; or both contain the same value.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;if optionalString == &amp;quot;Hello&amp;quot; {
    print(&amp;quot;It&amp;#x27;s a match!&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;10. Optional Collections&lt;/h3&gt;&lt;p&gt;You can have collections of optionals, allowing elements to be nil.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let ages: [Int?] = [25, nil, 47]
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;11. Optional Types with switch&lt;/h3&gt;&lt;p&gt;We can also handle optionals with a switch statement:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;switch name {
    case .some(let value):
        print(&amp;quot;name is \(value)&amp;quot;)
    case .none:
        print(&amp;quot;name is nil&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;12. Optionals under the hood&lt;/h3&gt;&lt;p&gt;To understand the above &lt;code&gt;switch&lt;/code&gt; statement better, the implementation of an optional.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Optional&amp;lt;Wrapped&amp;gt;: ExpressibleByNilLiteral {
    /// The absence of a value.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we look at the implementation of an optional, it is just an enum of two cases.&lt;/p&gt;&lt;p&gt;But wait. Why are we writing &lt;code&gt;Int?&lt;/code&gt; and not &lt;code&gt;Optional&amp;lt;Int&amp;gt;&lt;/code&gt;? Or why are we using &lt;code&gt;nil&lt;/code&gt; and not &lt;code&gt;.none&lt;/code&gt;?&lt;/p&gt;&lt;p&gt;In fact, we can do both. Both ways do the same. The second one is just syntactic sugar for ease of reading and writing code.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var age: Optional&amp;lt;Int&amp;gt; = .none
// is the same as
var age: Int? = nil

age = .some(5)
// is the same as
age = 5

&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swift</category><category>basics</category></item><item><title><![CDATA[Understanding container values in SwiftUI]]></title><description><![CDATA[Learn how to add customization to SwiftUI containers.]]></description><link>https://tanaschita.com/swiftui-container-values</link><guid isPermaLink="false">https://tanaschita.com/swiftui-container-values</guid><pubDate>Sun, 25 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the last article on &lt;a target=&quot;_blank&quot; href=&quot;/swiftui-custom-containers/&quot;&gt;building custom containers in SwiftUI&lt;/a&gt;, we explored the fundamentals of creating reusable containers in SwiftUI.&lt;/p&gt;&lt;p&gt;Now, we&amp;#x27;ll take it a step further by adding dynamic customization to container content using container values.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Recap: Basic custom container&lt;/h3&gt;&lt;p&gt;To start, let’s quickly recap a basic custom container that adds a background to each subview:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI

struct CustomContainer&amp;lt;Content: View&amp;gt;: View {
    @ViewBuilder var content: Content

    var body: some View {
        VStack {
            ForEach(subviewOf: content) { subview in
                subview
                    .background(.gray)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Introducing dynamic content customization&lt;/h3&gt;&lt;p&gt;Suppose we want to be able to dynamically customize individual subviews within the container, such as highlighting specific views:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;CustomContainer {
    Text(...)
        .highlight(true)
    Image(...)
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To enable this behavior, the custom container needs to be aware of which content views have called the &lt;code&gt;highlight&lt;/code&gt; modifier. This is where container values come into play.&lt;/p&gt;&lt;h3&gt;Defining a new container value&lt;/h3&gt;&lt;p&gt;Container values use keyed storage similar to environment values or preferences but with a key distinction: container values can only be accessed by their direct parent container.&lt;/p&gt;&lt;p&gt;To define a new container value, we can use the &lt;code&gt;@Entry&lt;/code&gt; property wrapper:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension ContainerValues {
    @Entry var isHighlighted: Bool = false
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we define &lt;code&gt;isHighlighted&lt;/code&gt; as a new container value with a default value of &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Additionally, we can create a convenient extension to set this property:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension View {
    func highlight(_ value: Bool) →&amp;gt; some View {
        containerValue(\.isHighlighted, value)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Accessing container values&lt;/h3&gt;&lt;p&gt;Within our custom container, we can access this value to apply conditional styling:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ForEach(subviewOf: content) { subview in
    subview
        .background(subview.containerValues.isHighlighted ? .green : .gray)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, any subview that has the &lt;code&gt;highlight(true)&lt;/code&gt; modifier applied will have a green background, while others will retain the default gray background.&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>ios</category></item><item><title><![CDATA[How to call async/await functions concurrently in Swift]]></title><description><![CDATA[Learn how to call async/await functions in parallel with tasks.]]></description><link>https://tanaschita.com/swift-call-async-await-functions-concurrently</link><guid isPermaLink="false">https://tanaschita.com/swift-call-async-await-functions-concurrently</guid><pubDate>Sun, 18 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In Swift, when we call an asynchronous function using &lt;code&gt;await&lt;/code&gt;, the function runs sequentially — meaning that each line of code waits for the previous one to complete before proceeding.&lt;/p&gt;&lt;p&gt;However, there are situations where we might want to run multiple asynchronous functions in parallel, especially when these functions don&amp;#x27;t depend on each other. This is where tasks come into play.&lt;/p&gt;&lt;p&gt;Lets dive in and see how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Executing asynchronous functions concurrently with tasks&lt;/h3&gt;&lt;p&gt;Tasks work hand in hand with Swift&amp;#x27;s async/await functionality. Each task runs concurrently, allowing us to execute asynchronous functions in parallel.&lt;/p&gt;&lt;p&gt;To create a task, we can use the &lt;strong&gt;async-let binding&lt;/strong&gt;. Here is how it looks in practice:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Sequential binding:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let profileImage = await someAsyncFunc()
let headerImage = await someAsyncFunc()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this sequential example, &lt;code&gt;profileImage&lt;/code&gt; is fetched first, and only after it completes, the &lt;code&gt;headerImage&lt;/code&gt; fetch begins.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Concurrent binding:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;async let profileImageTask = await someAsyncFunc()
async let headerImageTask = await someAsyncFunc()

let profileImage = await profileImageTask
let headerImage = await headerImageTask
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the concurrent binding approach, we created two child tasks that start independently without waiting for the previous one to complete.&lt;/p&gt;&lt;h3&gt;Group tasks&lt;/h3&gt;&lt;p&gt;Additionally to async-let tasks, Swift provides group tasks which are ideal for handling a dynamic number of concurrent tasks.&lt;/p&gt;&lt;p&gt;Group tasks are particularly useful when we don’t know the exact number of tasks beforehand, such as when downloading images from a list of URLs.&lt;/p&gt;&lt;p&gt;Here’s how we can use group tasks to download images concurrently:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func downloadImages(imageURLs: [URL]) async throws -&amp;gt; [UIImage] {
    var results: [UIImage] = []
    try await withThrowingTaskGroup(of: (Data, URLResponse).self, body: { taskGroup in
        for imageURL in imageURLs {
            taskGroup.async { try await URLSession.shared.data(from: imageURL) }
        }
        while let data = try await taskGroup.next(), let image = UIImage(data: data.0) {
            results.append(image)
        }
    })
    return results
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Downloading multiple images concurrently can drastically reduce the total time taken compared to downloading them sequentially. Using group tasks, we can achieve this concurrency in a clean and structured manner.&lt;/p&gt;</content:encoded><category>async/await</category><category>concurrency</category><category>swift</category></item><item><title><![CDATA[Understanding the @FetchRequest property wrapper in SwiftUI]]></title><description><![CDATA[Learn how to request data from the Core Data store in a SwiftUI view.]]></description><link>https://tanaschita.com/swiftui-fetchrequest-property-wrapper</link><guid isPermaLink="false">https://tanaschita.com/swiftui-fetchrequest-property-wrapper</guid><pubDate>Sun, 18 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;@FetchRequest&lt;/code&gt; property wrapper in SwiftUI simplifies the process of fetching data from a Core Data store for use in a SwiftUI view.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in and see how it works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Basic fetch request&lt;/h3&gt;&lt;p&gt;A basic fetch request to fetch a list of &lt;code&gt;Item&lt;/code&gt; entities looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@FetchRequest(
    sortDescriptors: [])
private var items: FetchedResults&amp;lt;Item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;@FetchRequest&lt;/code&gt; above is applied to the &lt;code&gt;items&lt;/code&gt; property of type &lt;code&gt;FetchedResults&amp;lt;Item&amp;gt;&lt;/code&gt;. Since &lt;code&gt;FetchedResults&lt;/code&gt; conforms to the &lt;code&gt;Collection&lt;/code&gt; protocol, we can use it just like an array in a &lt;code&gt;List&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;List {
    ForEach(items) { item in
        Text(&amp;quot;Item \(item.name)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Filtering and sorting with FetchRequest&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;FetchRequest&lt;/code&gt; initializer also supports filtering and sorting through its &lt;code&gt;sortDescriptors&lt;/code&gt; and &lt;code&gt;predicate&lt;/code&gt; parameters. Here’s how we can use them:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Item.date, ascending: true)],
    predicate: NSPredicate(format: &amp;quot;name BEGINSWITH %@&amp;quot;, &amp;quot;t&amp;quot;)
)
private var items: FetchedResults&amp;lt;Item&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This fetch request filters the results to include only items whose names begin with the letter &amp;quot;t&amp;quot; and sorts them by the &lt;code&gt;date&lt;/code&gt; attribute in ascending order.&lt;/p&gt;&lt;h3&gt;Important Considerations&lt;/h3&gt;&lt;p&gt;It’s important to note that both the fetch request and its results rely on the managed object context provided in the environment. Therefore, we must inject a Core Data managed object context into the environment before using @FetchRequest. To learn more about setting up Core Data in SwiftUI, check out our comprehensive guide on &lt;a href=&quot;/coredata-with-swiftui/&quot;&gt;integrating Core Data with SwiftUI&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>core data</category><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to create custom reusable container views in SwiftUI]]></title><description><![CDATA[Learn how to build a generic custom container in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-custom-containers</link><guid isPermaLink="false">https://tanaschita.com/swiftui-custom-containers</guid><pubDate>Sun, 11 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When building iOS apps with SwiftUI, we sometimes need to create a custom reusable container view which can be used as any other SwiftUI container view with any kind of content.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore how to create such containers.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s dive in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Understanding the basics of container views in SwiftUI&lt;/h3&gt;&lt;p&gt;In SwiftUI, container views are components that encapsulate and manage the layout of their child views, arranging them in a certain way. Common examples include &lt;code&gt;HStack&lt;/code&gt;, &lt;code&gt;VStack&lt;/code&gt; or &lt;code&gt;List&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;When creating custom container views, SwiftUI provides methods to access contained child views, sections or container values which allow a high level of customization.&lt;/p&gt;&lt;h3&gt;Creating a simple custom container&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s start by creating a simple custom container view which adds some styling to the content view.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import SwiftUI

struct CustomContainer&amp;lt;Content: View&amp;gt;: View {
    @ViewBuilder var content: Content

    var body: some View {
        VStack {
            content
        }
        .padding()
        .background(...)
        .cornerRadius(10)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, our container takes a closure returning a &lt;code&gt;Content&lt;/code&gt; view. The &lt;code&gt;@ViewBuilder&lt;/code&gt; attribute allows the closure to return multiple child views:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;CustomContainer {
    Text(...)
    Image(...)
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Adding custom styling to each subview&lt;/h3&gt;&lt;p&gt;SwiftUI allows access to each subview passed into the container, enabling us to apply specific styling to each one. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ForEach(subviewOf: content) { subview in
     subview
        .background(...)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Adding sections&lt;/h3&gt;&lt;p&gt;Additionally, SwiftUI gives us access to each &lt;code&gt;Section&lt;/code&gt; view and its content that was defined within the container:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ForEach(sectionOf: content) { section in
    VStack {
        Text(section.header)
        section.content
            .background(...)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Container values&lt;/h3&gt;&lt;p&gt;To add more customization, we can use container values. Container values give us the possibility to configure specific child views. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;CustomContainer {
    Text(...)
        .highlight(true)
    Image(...)
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Within the container, we can access container values as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;ForEach(subviewOf: content) { subview in
    subview
        .background(subview.containerValues.isHighlighted ? .green : .grey)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To dive deeper into container values, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/swiftui-container-values/&quot;&gt;understanding container values in SwiftUI&lt;/a&gt; (coming soon).&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>ios</category></item><item><title><![CDATA[How to add local Swift Packages to an iOS project]]></title><description><![CDATA[Learn how to add local dependencies to your iOS project by using Swift Package Manager and Xcode]]></description><link>https://tanaschita.com/spm-add-local-packages</link><guid isPermaLink="false">https://tanaschita.com/spm-add-local-packages</guid><pubDate>Sun, 04 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Swift Package Manager not only supports &lt;a href=&quot;/spm-add-public-packages/&quot; target=&quot;_blank&quot;&gt;adding public libraries as Swift Packages&lt;/a&gt;, but also local Swift Packages.&lt;/p&gt;&lt;p&gt;This feature is particularly useful, when splitting a project into multiple libraries without having to worry about versioning and being able to directly edit the code.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;To add a local Swift package to an iOS project, we can follow these steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Create a local Swift Package. This step is described in the following article on &lt;a href=&quot;/spm-overview/&quot; target=&quot;_blank&quot;&gt;getting started with Swift Package Manager&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Build the Swift package, for example by hitting Xcode&amp;#x27;s build action.&lt;/li&gt;&lt;li&gt;Open Xcode&amp;#x27;s &lt;code&gt;Package Dependencies&lt;/code&gt; section in your iOS project and tap the &lt;code&gt;+&lt;/code&gt; button.&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/5e1ab/spm-update-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:26.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA3ElEQVQY02WP21KDMBRF+x/SEpKQK5wUQduK45P6/3+0HDIt4+jDetrn7MthGIXxvDDMK/KyEoaC6U4YrbBaEZwlBsdQninzFR8SWh2rtmF2WnrbcZAiyBAwqsFbxVQyKXp0d8L1Gu8M3lmSTMhyJaaM1W3VtWru4e3OYXm9sL7diNGTU6httuMtfTqP5OSJoSeHHt0+MUnm++uTj/cb03whxVCDd8MiCRkTnTpW4bcYfE+KrrKFud7gvUUkUySTcsY5W6fuhptRrf+nurnP2vQHjzvVNpVONf/+fgAWU5CCEgHJIQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Package dependencies section&quot; title=&quot;Package dependencies section&quot; src=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/2e9ed/spm-update-package.png&quot; srcSet=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/12d18/spm-update-package.png 375w,/static/cde1009c5abe84f99fa5dedc8a9732d6/44507/spm-update-package.png 750w,/static/cde1009c5abe84f99fa5dedc8a9732d6/2e9ed/spm-update-package.png 1500w,/static/cde1009c5abe84f99fa5dedc8a9732d6/e90d0/spm-update-package.png 2250w,/static/cde1009c5abe84f99fa5dedc8a9732d6/5e1ab/spm-update-package.png 2256w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Use the &lt;code&gt;Add local&lt;/code&gt; button to select and add the local library.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;When successfully added, the local package will appear alongside any other libraries you have added previously.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f7150c5c9d57f5f28a59f9d9d0309a78/8e621/spm-local-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:20.266666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA0ElEQVQY01WPSWrEMBRE+xQJxpqtyJbUlrHj9BAa4k02OUMukPsf4AWrIcOi+FAU9V8dhhRx3qONJoSOUjJ97zFG4pwhxkBKkemycdo+CLkgRYNSLVq1NfdXh7x9ot++eMjvxKEjhHuZtYqUepzTWKNwViLbR3Lqud1emedCzEe8dzVjjLoXzstCHgtCScYxMU1jpdoL13Xhej2xrjPHMeGfHDkP1TufXyjzcwXoOvNLaIzAWlnx90/e25/AXloJraoTd2+/QjRVSjZoLf5N/gZF/XMYj69QSgAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Added local package&quot; title=&quot;Added local package&quot; src=&quot;/static/f7150c5c9d57f5f28a59f9d9d0309a78/2e9ed/spm-local-package.png&quot; srcSet=&quot;/static/f7150c5c9d57f5f28a59f9d9d0309a78/12d18/spm-local-package.png 375w,/static/f7150c5c9d57f5f28a59f9d9d0309a78/44507/spm-local-package.png 750w,/static/f7150c5c9d57f5f28a59f9d9d0309a78/2e9ed/spm-local-package.png 1500w,/static/f7150c5c9d57f5f28a59f9d9d0309a78/8e621/spm-local-package.png 2234w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;Now, you can use the local package in your iOS project just like any public library:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;import ExampleSwiftPackage
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: if you need to edit and rebuild the library, ensure you close the iOS project first.&lt;/p&gt;</content:encoded><category>swift package manager</category><category>xcode</category><category>ios</category><category>architecture</category></item><item><title><![CDATA[How to add public libraries as Swift Packages to an iOS project]]></title><description><![CDATA[Learn how to add, update and delete remote dependencies using the Swift Package Manager and Xcode]]></description><link>https://tanaschita.com/spm-add-public-packages</link><guid isPermaLink="false">https://tanaschita.com/spm-add-public-packages</guid><pubDate>Sun, 04 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As we learned in this &lt;a href=&quot;/spm-overview/&quot; target=&quot;_blank&quot;&gt;guide on getting started with the Swift Package Manager for iOS&lt;/a&gt;, we can add public libraries to our iOS projects by using the Swift Package Manager.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look how to add, update and delete remote dependencies in our iOS projects.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Adding a remote Swift Package&lt;/h3&gt;&lt;p&gt;To add a public library which is available on GitHub as a Swift Package requires the following steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Copy the documented Swift Package url of the library. (documented in the README of the library if it supports SPM)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;In Xcode, tap on the project and open &lt;code&gt;Package Dependencies&lt;/code&gt; section. Here, we can use the + button to add a new package.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/5e1ab/spm-update-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:26.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA3ElEQVQY02WP21KDMBRF+x/SEpKQK5wUQduK45P6/3+0HDIt4+jDetrn7MthGIXxvDDMK/KyEoaC6U4YrbBaEZwlBsdQninzFR8SWh2rtmF2WnrbcZAiyBAwqsFbxVQyKXp0d8L1Gu8M3lmSTMhyJaaM1W3VtWru4e3OYXm9sL7diNGTU6httuMtfTqP5OSJoSeHHt0+MUnm++uTj/cb03whxVCDd8MiCRkTnTpW4bcYfE+KrrKFud7gvUUkUySTcsY5W6fuhptRrf+nurnP2vQHjzvVNpVONf/+fgAWU5CCEgHJIQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Adding a new package&quot; title=&quot;Adding a new package&quot; src=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/2e9ed/spm-update-package.png&quot; srcSet=&quot;/static/cde1009c5abe84f99fa5dedc8a9732d6/12d18/spm-update-package.png 375w,/static/cde1009c5abe84f99fa5dedc8a9732d6/44507/spm-update-package.png 750w,/static/cde1009c5abe84f99fa5dedc8a9732d6/2e9ed/spm-update-package.png 1500w,/static/cde1009c5abe84f99fa5dedc8a9732d6/e90d0/spm-update-package.png 2250w,/static/cde1009c5abe84f99fa5dedc8a9732d6/5e1ab/spm-update-package.png 2256w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;ol start=&quot;3&quot;&gt;&lt;li&gt;Decide how the package should be updated, for example up to the next major version, up to the next minor version, in a specified range or restricting it to the current version.&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/435bff8ee0e052d5eb4ccba5c11840e9/819be/spm-add-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:55.733333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB7UlEQVQoz42TXW/TMBiF+x+AobVrPv2RNHWSpkmbbmxrE9imMTZAaEK7YEjcgOCG/3/xIHudNgk0cfHotWz5+Njv8aCeVywXDbnJSLWkzDOWTUWRZ2gVI+MQJUKkCIkCj8AbEQVjTJY6pllKYTK3f5IqBkVumJU51SxHiIiLruH2+i3rTU/Xbej7nr7v6LqOfDqhMBO0jKhKw6KpKZsVialRE0MUCwZKxu50ixIRqY6ZThRpIrcIVxMtEHFAPS9oqoqry/fcfP7Eu6uPlM0RKvaRccBAK+muZk+1hMEY3xsS+KO/CP09wvEInUuEEXijXXxv18373ohExdahFRSorWAceUTh+J+EwR4vnj/j6NcH1j+v2dkZ4nshyeglgTdESyconMPHgnbjY5H7KkRIV2ZcrhvawxXloaI8liTVAV4Uoe2V/1fQPsN0OuHrh0u+X5zy7eaG299feHU+w7RrdJaRyuhpQVvvx/YN7fqiXdCslrSrBW3dctTuc7iqaaqcVIs7QfWEw3ucwyzh/OyEk9cbx9lpz8mbDQf7S0ym0SJ86PKDoH/XmODBocU6tAG3eZ0VNruGWTFlPsuZVyVpmiDjiEGiFYmWrkM2RyZL6LtjZ9/f/goXly3+eOh+i8XbG7KoDGW7Jjn9gaw7/gBw6keiNyx/2gAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Adding a new package&quot; title=&quot;Adding a new package&quot; src=&quot;/static/435bff8ee0e052d5eb4ccba5c11840e9/2e9ed/spm-add-package.png&quot; srcSet=&quot;/static/435bff8ee0e052d5eb4ccba5c11840e9/12d18/spm-add-package.png 375w,/static/435bff8ee0e052d5eb4ccba5c11840e9/44507/spm-add-package.png 750w,/static/435bff8ee0e052d5eb4ccba5c11840e9/2e9ed/spm-add-package.png 1500w,/static/435bff8ee0e052d5eb4ccba5c11840e9/819be/spm-add-package.png 2150w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;After confirming, Xcode fetches the library automatically. The fetched dependencies are stored in the project’s derived data directory. Xcode also adds it to the &lt;code&gt;Package.resolved&lt;/code&gt; file to ensure every developer has the same dependency versions.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Now, we can use the package functionality by importing it as a Swift module in our code files.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;import Fakery
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Updating or deleting a Swift Package&lt;/h3&gt;&lt;p&gt;To update single packages to specific versions or delete them, we can use the same dialog shown above. To update all packages to the latest versions, Xcode provides the menu action &lt;code&gt;File &amp;gt; Swift Packages &amp;gt; Update to Latest Package Versions&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>swift package manager</category><category>xcode</category><category>ios</category><category>architecture</category></item><item><title><![CDATA[Get started with Swift Package Manager for iOS]]></title><description><![CDATA[Learn how to create and manage Swift Packages.]]></description><link>https://tanaschita.com/spm-overview</link><guid isPermaLink="false">https://tanaschita.com/spm-overview</guid><pubDate>Sun, 04 Aug 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Provided by Apple, Swift Package Manager is a dependency manager for Swift projects which is directly integrated into Xcode.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll learn the main concepts of the Swift Package Manager, how to create Swift packages, how to share and use packages across different projects.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Main concepts&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Swift Packages&lt;/strong&gt; are reusable components containing resources and code files that can also depend on other packages.&lt;/p&gt;&lt;p&gt;Compiling a package produces a &lt;strong&gt;module&lt;/strong&gt; which can be shared as a public library using services like GitHub or be used as a locale dependency.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The manifest&lt;/strong&gt; - a file called &lt;code&gt;Package.swift&lt;/code&gt; - defines the name of the package, its contents and dependencies. It is placed in the root directory of each package.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s see how this manifest file looks like by creating a Swift Package.&lt;/p&gt;&lt;h3&gt;Creating a Swift Package&lt;/h3&gt;&lt;p&gt;One way to create a new Swift Package is by selecting &lt;code&gt;File &amp;gt; New &amp;gt; Swift Package&lt;/code&gt; in Xcode. After entering the name, Xcode generates all necessary files and folders together with the manifest file &lt;code&gt;Package.swift&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// swift-tools-version:6.0

import PackageDescription

let package = Package(
    name: &amp;quot;SnowKit&amp;quot;,
    // 1.
    products: [
        .library(
            name: &amp;quot;SnowKit&amp;quot;,
            targets: [&amp;quot;SnowKit&amp;quot;]),
    ],
    // 2.
    dependencies: [
    ],
    // 3.
    targets: [
        .target(
            name: &amp;quot;SnowKit&amp;quot;,
            dependencies: []),
        .testTarget(
            name: &amp;quot;SnowKitTests&amp;quot;,
            dependencies: [&amp;quot;SnowKit&amp;quot;]),
    ]
)
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;In the &lt;code&gt;products&lt;/code&gt; part, we define executables and libraries a package produces.&lt;/li&gt;&lt;li&gt;In the &lt;code&gt;dependencies&lt;/code&gt; part, we declare other packages that our package depends on.&lt;/li&gt;&lt;li&gt;In the &lt;code&gt;targets&lt;/code&gt; part, we declare targets the package consists of, for example one for our library and one for our tests.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Adding dependencies to the Swift Package&lt;/h3&gt;&lt;p&gt;If a package depends on other packages, we can add them in the &lt;code&gt;dependencies&lt;/code&gt; section of the manifest file.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;dependencies: [
    .package(url: &amp;quot;https://github.com/RainbowKit.git&amp;quot;, .upToNextMajor(from: &amp;quot;5.2.0&amp;quot;))
]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use the library by importing it into the source code files just as we would in an iOS project.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import RainbowKit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can also add local packages as dependencies.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;dependencies: [
    .package(path: &amp;quot;../SunshineKit&amp;quot;)
]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach is useful when splitting a project into multiple libraries, allowing us to edit the package&amp;#x27;s files directly within the project without worrying about versioning.&lt;/p&gt;&lt;h3&gt;Using Swift Packages in iOS Projects&lt;/h3&gt;&lt;p&gt;Depending on the project requirements, there are different ways to use Swift Packages in an iOS project.&lt;/p&gt;&lt;p&gt;To &lt;strong&gt;add a public library as a Swift Package&lt;/strong&gt;, Xcode provides the &lt;code&gt;Package Dependencies&lt;/code&gt; section to add, update and delete Swift packages. Check out this article on &lt;a href=&quot;/spm-add-public-packages/&quot; target=&quot;_blank&quot;&gt;how to add public libraries as Swift Packages to an iOS project&lt;/a&gt; for more details.&lt;/p&gt;&lt;p&gt;Swift Package Manager also supports &lt;strong&gt;local packages&lt;/strong&gt;, which is useful for organizing our code in a modular way. This involves dragging and dropping the local package into the Xcode project and adding it in the &lt;code&gt;Frameworks, Libraries, and Embedded Content&lt;/code&gt; section. For more details, check out this article on &lt;a href=&quot;/spm-add-local-packages/&quot; target=&quot;_blank&quot;&gt;how to add a local Swift Package to an iOS project&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>swift package manager</category><category>xcode</category><category>architecture</category><category>ios</category></item><item><title><![CDATA[Understanding the @Environment property wrapper in SwiftUI]]></title><description><![CDATA[Learn different scenarios for using the @Environment property wrapper.]]></description><link>https://tanaschita.com/swiftui-environment-property-wrapper</link><guid isPermaLink="false">https://tanaschita.com/swiftui-environment-property-wrapper</guid><pubDate>Sun, 28 Jul 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;@Environment&lt;/code&gt; property wrapper in SwiftUI allows data sharing between views without explicitly passing the data down the view hierarchy. Setting an environment property on a view makes it accessible to that view and all its subviews.&lt;/p&gt;&lt;p&gt;There are different scenarios for using the &lt;code&gt;@Environment&lt;/code&gt; property wrapper:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Using predefined &lt;code&gt;@Environment&lt;/code&gt; values in SwiftUI&lt;/li&gt;&lt;li&gt;Using custom &lt;code&gt;@Environment&lt;/code&gt; values&lt;/li&gt;&lt;li&gt;Using &lt;code&gt;@Environment&lt;/code&gt; objects&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s explore each scenario in more detail.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;1. Using one of SwiftUI&amp;#x27;s predefined &lt;code&gt;@Environment&lt;/code&gt; values&lt;/h3&gt;&lt;p&gt;SwiftUI provides predefined values that we can use just out of the box like &lt;code&gt;colorScheme&lt;/code&gt;, &lt;code&gt;managedObjectContext&lt;/code&gt;, &lt;code&gt;timeZone&lt;/code&gt;, &lt;code&gt;openURL&lt;/code&gt; and more.&lt;/p&gt;&lt;p&gt;For example, we can access the current color scheme by using the &lt;code&gt;colorScheme&lt;/code&gt; property:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {

    @Environment(\.colorScheme) private var colorScheme

    var body: some View {
        VStack {
            Text(colorScheme == .dark ? &amp;quot;dark&amp;quot; : &amp;quot;light&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Using custom &lt;code&gt;@Environment&lt;/code&gt; values&lt;/h3&gt;&lt;p&gt;While SwiftUI provides many built-in environment values, there are times when we need to create custom ones to suit our specific needs.&lt;/p&gt;&lt;p&gt;This involves creating an environment key, injecting the custom value into views, and using it similarly to predefined values:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(\.theme) private var theme
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To learn more, check out this article on &lt;a href=&quot;/swiftui-custom-environment-values/&quot;&gt;how to create custom @Environment values in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;3. Using &lt;code&gt;@Environment&lt;/code&gt; objects&lt;/h3&gt;&lt;p&gt;We can also pass reference objects between views using &lt;code&gt;@Environment&lt;/code&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var viewModel = ViewModel()

SomeView()
    .environment(viewModel)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can access it without using keys:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(ViewModel.self) private var viewModel
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This method is useful for sharing complex objects that manage state or handle logic across multiple views. It ensures that all views are referencing the same instance, keeping the data consistent.&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to delay server requests for user's search query with SwiftUI and Combine]]></title><description><![CDATA[Learn how to debounce on a practical example.]]></description><link>https://tanaschita.com/combine-swiftui-search-query-debounce</link><guid isPermaLink="false">https://tanaschita.com/combine-swiftui-search-query-debounce</guid><pubDate>Sun, 14 Jul 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Search is a common feature in many applications. When a user types in a search query, we often want to avoid sending a new server request every time the query changes. Instead, we might want to delay it the request and only send it after the user has stopped typing for a certain amount of time.&lt;/p&gt;&lt;p&gt;This is where Combine&amp;#x27;s debounce operator comes in. It helps us achieve precisely this behavior. Let&amp;#x27;s see how we can implement it in combination with SwiftUI.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;SwiftUI search implementation&lt;/h3&gt;&lt;p&gt;When working with SwiftUI, our UI code for searching might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SearchView: View {

    @State private var userSearchInput: String = &amp;quot;&amp;quot;

    var body: some View {
        VStack {
            // Show loading indicator, error, or results
        }
        .searchable(text: $userSearchInput)
        .onChange(of: userSearchInput) { _, newValue in
            viewModel.updateSearch(userSearchInput: newValue)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;We use SwiftUI&amp;#x27;s &lt;code&gt;searchable&lt;/code&gt; modifier to configure the display of a search field.&lt;/li&gt;&lt;li&gt;We use a &lt;code&gt;@State&lt;/code&gt; variable to pass in as a binding to the &lt;code&gt;searchable&lt;/code&gt; modifier. It will get updated automatically as the user changes the search query.&lt;/li&gt;&lt;li&gt;We subscribe to changes of &lt;code&gt;userSearchInput&lt;/code&gt; to notify our view model that it might need to refresh the results.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;ViewModel debounce implementation&lt;/h3&gt;&lt;p&gt;Next, let&amp;#x27;s look at how the view model might be implemented:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class SearchViewModel {
    private var userSearchInputSubject = PassthroughSubject&amp;lt;String?, Never&amp;gt;()
    private var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()

    init() {
        userSearchInputSubject
            .debounce(for: 0.5, scheduler: RunLoop.main)
            .sink { [weak self] searchInput in
                self?.reload(with: searchInput)
            }
            .store(in: &amp;amp;cancellables)
    }

    func updateSearch(userSearchInput: String?) {
        userSearchInputSubject.send(userSearchInput)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By using the debounce operator, we ensure that the server requests are sent only when the user has stopped typing for half a second, reducing the number of unnecessary requests and improving the overall performance of our application.&lt;/p&gt;</content:encoded><category>combine</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Backpressure in Combine]]></title><description><![CDATA[Understand the concept behind backpressure and how to apply it in Combine.]]></description><link>https://tanaschita.com/combine-back-pressure</link><guid isPermaLink="false">https://tanaschita.com/combine-back-pressure</guid><pubDate>Sun, 07 Jul 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with subscribers in Combine, we mostly use &lt;code&gt;sink(receiveValue:)&lt;/code&gt; and &lt;code&gt;assign(to:on:)&lt;/code&gt; to receive an unlimited amount of values from a publisher. However, there are scenarios where processing these values takes longer, and new values continue to arrive.&lt;/p&gt;&lt;p&gt;To prevent blocking or overflow, we may need to control the rate at which values are received. This concept of limiting the elements the subscriber receives is called &lt;em&gt;backpressure&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at some examples and strategies on how to solve them.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Examples of backpressure&lt;/h3&gt;&lt;p&gt;Backpressure issues may arise in various scenarios, such as:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Reading and writing data&lt;/strong&gt;: When accessing slow storage media.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Server communication&lt;/strong&gt;: When handling real-time data from a server.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rendering&lt;/strong&gt;: When updating the UI frequently.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For instance, in real-time server-to-client communication using WebSockets, emitting numerous values per second can lead to performance issues if the UI is updated with each incoming value. A better approach would be to buffer these values and update the UI at a controlled rate.&lt;/p&gt;&lt;p&gt;Another practical example is handling user search input. Instead of sending a server request with every keystroke, we can wait until the user stops typing for a specific amount of time before making a request.&lt;/p&gt;&lt;h3&gt;Backpressure strategies&lt;/h3&gt;&lt;p&gt;There are several strategies for applying backpressure in Combine, including:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Buffer&lt;/strong&gt; i.e. accumulate incoming values temporarily&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Drop&lt;/strong&gt; i.e. skipping some of the incoming values&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Both strategies can be implemented in different ways, either by creating a custom subscriber or by using Combine’s buffering or timing operators.&lt;/p&gt;&lt;h3&gt;Using Combine&amp;#x27;s buffering and timing operators&lt;/h3&gt;&lt;p&gt;Combine provides operators that can be attached to a standard subscriber to apply backpressure.&lt;/p&gt;&lt;h4&gt;The &lt;code&gt;debounce(for:scheduler:options:)&lt;/code&gt; operator&lt;/h4&gt;&lt;p&gt;This operator publishes elements only after a specified time interval elapses. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cancellable = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { value in
        print(&amp;quot;received \(value)&amp;quot;)
    }
subject.send(1)
subject.send(2)

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.subject.send(3)
    self.subject.send(4)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;received 2
received 4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Values 1 and 3 are dropped due to the 0.5-second debounce interval.&lt;/p&gt;&lt;h4&gt;The &lt;code&gt;throttle(for:scheduler:latest:)&lt;/code&gt; operator&lt;/h4&gt;&lt;p&gt;This operator produces one element per specified interval, sending either the newest or oldest element received during that interval.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;cancellable = Timer.publish(every: 1.0, on: .main, in: .default)
        .autoconnect()
        .throttle(for: 3.0, scheduler: RunLoop.main, latest: true)
        .sink{ date in
            print(&amp;quot;received \(date)&amp;quot;)
        }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;received 2021-11-10 18:36:16
received 2021-11-10 18:36:19
received 2021-11-10 18:36:22
received 2021-11-10 18:36:25
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We receive a date value every 3 seconds.&lt;/p&gt;&lt;h4&gt;The &lt;code&gt;collect(_:)&lt;/code&gt; operator&lt;/h4&gt;&lt;p&gt;This operator bundles elements into an array and emits them based on a specified count or time interval.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;cancellable = subject
    .collect(2)
    .sink { value in
        print(&amp;quot;received \(value)&amp;quot;)
    }

subject.send(1)
subject.send(2)
subject.send(3)
subject.send(4)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;received [1, 2]
received [3, 4]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach is useful if the subscriber can process multiple elements at the same time.&lt;/p&gt;&lt;h3&gt;Creating a custom subscriber to apply back pressure&lt;/h3&gt;&lt;p&gt;If none of the above operators meets our needs, we can implement a custom subscriber that applies backpressure. Here is an example of a subscriber which receives one value initially and then two additional values after a second:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class CountSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never
    var subscription: Subscription?

    // 1.
    func receive(subscription: Subscription) {
        print(&amp;quot;subscribed&amp;quot;)
        self.subscription = subscription
        subscription.request(.max(1))
    }

    // 2.
    func receive(_ input: Int) -&amp;gt; Subscribers.Demand {
        print(&amp;quot;received \(input)&amp;quot;)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.subscription?.request(.max(2))
        }
        return Subscribers.Demand.none
    }

    // 3.
    func receive(completion: Subscribers.Completion&amp;lt;Never&amp;gt;) {
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s go though it step by step:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;receive(subscription:)&lt;/code&gt;: Called as soon as a new subscription arrives. We request one element from the publisher.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;receive(_ input:)&lt;/code&gt;: Called when an element is received. We request two additional values after one second.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;receive(completion:)&lt;/code&gt;: Called when the publisher completes.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Using the subscriber might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let subject = PassthroughSubject&amp;lt;Int, Never&amp;gt;()
let subscriber = CountSubscriber()
subject.subscribe(subscriber)
subject.send(1)
subject.send(2)

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    self.subject.send(3)
    self.subject.send(4)
    self.subject.send(5)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;subscribed
received 1
received 3
received 4
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>combine</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Developer guide on Swift Testing for iOS]]></title><description><![CDATA[Learn how use Swift Testing to write automated tests.]]></description><link>https://tanaschita.com/testing-swift-testing-overview</link><guid isPermaLink="false">https://tanaschita.com/testing-swift-testing-overview</guid><pubDate>Sun, 30 Jun 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC 2024, Apple released Swift Testing - a new framework which allows us to write automated tests for iOS applications.&lt;/p&gt;&lt;p&gt;Starting with Xcode 16, Swift Testing is now the default Testing framework when we add a testing target to our iOS project.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and look at how test functions look in Swift Testing.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Test functions&lt;/h3&gt;&lt;p&gt;An example of a test with Swift Testing looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Testing
@testable import ExampleApp

struct ExampleAppTests {

    @Test func testExample() async throws {
        ...
        expect(resultName == expectedName)
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A test function follows these rules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the &lt;code&gt;@Test&lt;/code&gt; annotation indicates that a function is a test.&lt;/li&gt;&lt;li&gt;a test function can be a global function or a method in a type&lt;/li&gt;&lt;li&gt;a test function can be &lt;code&gt;async&lt;/code&gt;&lt;/li&gt;&lt;li&gt;a test function can &lt;code&gt;throw&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Expectations&lt;/h3&gt;&lt;p&gt;To validate expected conditions, we can use the &lt;code&gt;#expect&lt;/code&gt; macro. It accepts ordinary expressions and operators, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;#expect(!array.isEmpty)
#expect(greeting == &amp;quot;Hello&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively, we can use the &lt;code&gt;#require&lt;/code&gt; macro to throw an error and to stop the test if the condition is false. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;try #require(!result.isEmpty)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can also use the &lt;code&gt;#require&lt;/code&gt; macro to unwrap an optional:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let element = try #require(array.first)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Traits&lt;/h3&gt;&lt;p&gt;We can add traits to the test, for example to describe a test or to customize how it behaves. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;@Test(&amp;quot;An example test&amp;quot;)&lt;/code&gt; to customize the display name of a test&lt;/li&gt;&lt;li&gt;&lt;code&gt;@Test(.tags(.critical))&lt;/code&gt; to add a custom tag to the test&lt;/li&gt;&lt;li&gt;&lt;code&gt;@Test.enabled(if: someCondition))&lt;/code&gt; to conditionally enable or disable a test&lt;/li&gt;&lt;li&gt;&lt;code&gt;@Test.disabled(&amp;quot;Describe why disabled&amp;quot;))&lt;/code&gt; to skip a test&lt;/li&gt;&lt;li&gt;&lt;code&gt;@Test(.timeLimit(.minutes(1)))&lt;/code&gt; to add a time limit&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Suits&lt;/h3&gt;&lt;p&gt;To group related test functions we can use suites. A suite is implicitly created when we wrap test functions for example into a struct:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ExampleTests {

    @Test func test1() async throws {
        ...
    }

    @Test func test2() {
        ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With suites, we can use init and deinit functions for setup and teardown logic.&lt;/p&gt;&lt;h3&gt;Parameterized testing&lt;/h3&gt;&lt;p&gt;Swift Testing supports parameterized tests allowing us to run the same test logic multiple times with different input values:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Test(arguments: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;])
func example(input: String) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Migrating from XCTest&lt;/h3&gt;&lt;p&gt;Swift Testing tests can coexist with XCTest in a single unit test target. This helps us migrate tests incrementally.&lt;/p&gt;&lt;p&gt;Swift Testing does currently not support Ul automation APls such as &lt;code&gt;XCUIApplication&lt;/code&gt; or performance testing APls such as &lt;code&gt;XCMetric&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>testing</category><category>swift</category><category>ios</category></item><item><title><![CDATA[WWDC 2024 summary of Apple's platforms state of the union talk for iOS developers]]></title><description><![CDATA[Discover Apple's announcements for iOS at WWDC 2024.]]></description><link>https://tanaschita.com/wwdc-2024</link><guid isPermaLink="false">https://tanaschita.com/wwdc-2024</guid><pubDate>Sun, 23 Jun 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article summerizes the announcements for iOS at Apple&amp;#x27;s platforms state of the union talk at WWDC 2024.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Apple Intelligence&lt;/h3&gt;&lt;p&gt;Apple Intelligence is Apple&amp;#x27;s suite of AI technologies that improve user experience by powering features like Siri, image recognition, and personalized recommendations across their devices.&lt;/p&gt;&lt;p&gt;As iOS developers, we can use Apple Intelligence in our apps, for example by using the &lt;strong&gt;Image Playground API&lt;/strong&gt; which allows us to add Image Playgrounds to our apps to let users generate their own new images from text input directly on device:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Button(...)
    .imagePlaygroundSheet (isPresented: $isImagePlaygroundPresented)｛ createdImageURL in
        // Use generated image. The created url is a local url which we can access with FileManager.
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Further examples to use Apple Intelligence in an iOS app are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Genmoji&lt;/strong&gt; which lets users create new emojis from their text input&lt;/li&gt;&lt;li&gt;Siri with App Intents&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Predictive Code Completion &amp;amp; Swift Assist&lt;/h3&gt;&lt;p&gt;Xcode 16 introduces &lt;strong&gt;Predictive Code Completion&lt;/strong&gt; which uses machine learning to provide more accurate and context-aware suggestions.&lt;/p&gt;&lt;p&gt;Additionally, Xcode 16 comes with &lt;strong&gt;Swift Assist&lt;/strong&gt; which uses machine learning provide code suggestions from our text input. Since Swift Assist runs in the cloud, it knows all the latest SDK updates and Swift Language features.&lt;/p&gt;&lt;h3&gt;Swift Testing&lt;/h3&gt;&lt;p&gt;Swift Testing is Apple&amp;#x27;s new testing framework which is an open-source package. An example of a test with Swift Testing looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Test(&amp;quot;test long outputs of MinutesFormatter&amp;quot;,
    arguments: [
        (1, &amp;quot;eine Minute&amp;quot;),
        (2, &amp;quot;zwei Minuten&amp;quot;)
])
func testMinutesFormatter(minutes: Int, expected: String) {
    let formatter = MinutesFormatter(lang: &amp;quot;de&amp;quot;)
    #expect(formatter.longOutput(from: minutes) == expected)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Swift Testing also works side-by-side with XCTest which helps us migrate tests incrementally.&lt;/p&gt;&lt;h3&gt;Swift 6&lt;/h3&gt;&lt;p&gt;Swift has its 10th birthday this year. Swift 6 introduces data-race safety by diagnosing potencial data races at compile time, further concurrency improvements, typed throws, increased interoperability with other languages and more.&lt;/p&gt;&lt;h3&gt;SwiftUI updates&lt;/h3&gt;&lt;p&gt;SwiftUI comes with improvements on many different topics such as navigation transitions, animation interoperability, programmatic text selection, timer formats, scrolling refinements and more.&lt;/p&gt;&lt;h3&gt;SwiftData updates&lt;/h3&gt;&lt;p&gt;SwiftData introduces new marcos such as &lt;code&gt;#Index&lt;/code&gt; and &lt;code&gt;#Unique&lt;/code&gt;. Additionally, we now get access to the history of local changes.&lt;/p&gt;&lt;h3&gt;RealityKit updates&lt;/h3&gt;&lt;p&gt;RealityKit 4 allows building for iOS, iPadOS, macOS, and visionOS simultaneously.&lt;/p&gt;&lt;p&gt;Reality Composer Pro, introduced with Apple Vision Pro, supports spatial app development across these platforms. Features like MaterialX shaders, portals, and particles work with RealityView on all four platforms. This includes existing APIs, as well as new features like blend shapes, inverse kinematics, skeletal poses and animation timelines.&lt;/p&gt;&lt;p&gt;Additionally, Xcode view debugging now includes 3D scene content inspection, simplifying the debugging process.&lt;/p&gt;&lt;h3&gt;Controls API&lt;/h3&gt;&lt;p&gt;The new Controls API allows us to add controls to device system spaces such as Control Center and Lock Screen. Controls can for example toggle a setting, execute an action or trigger a deep link. We can use the new &lt;code&gt;ControlWidget&lt;/code&gt; type to configure controls.&lt;/p&gt;&lt;h3&gt;Tinted App Icons&lt;/h3&gt;&lt;p&gt;iOS now supports tinted app icons which can be configured in Xcode additionally to Light and Dark Mode app icons.&lt;/p&gt;</content:encoded><category>wwdc</category><category>ios</category></item><item><title><![CDATA[How to use NavigationPath for routing in SwiftUI]]></title><description><![CDATA[Learn how to manage navigation in SwiftUI programmatically.]]></description><link>https://tanaschita.com/swiftui-navigationpath</link><guid isPermaLink="false">https://tanaschita.com/swiftui-navigationpath</guid><pubDate>Sat, 08 Jun 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With &lt;code&gt;NavigationPath&lt;/code&gt;, SwiftUI provides a robust and flexible way to handle navigation and routing. It allows us to programmatically control the navigation stack without the need to use a &lt;code&gt;NavigationLink&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll explore how to use &lt;code&gt;NavigationPath&lt;/code&gt; in a practical example.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;When creating a &lt;code&gt;NavigationStack&lt;/code&gt;, we have the possibility to pass in a &lt;code&gt;path&lt;/code&gt; parameter:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeView: View {

    @State private var navigationPath = NavigationPath()

    var body: some View {
        NavigationStack(path: $navigationPath) {
            ...
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The passed in &lt;code&gt;path&lt;/code&gt; is a &lt;code&gt;Binding&lt;/code&gt; to the navigation state for this stack - enabling us to control the navigation stack by appending data to it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;navigationPath.append(route)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s say, we are building an app which can navigate to a recipes search - which in turn can navigate to a recipe detail when a user selects one of the results.&lt;/p&gt;&lt;p&gt;The approach to control this flow could look as follows:&lt;/p&gt;&lt;h3&gt;1. Defining an enum which represents all possible navigation routes&lt;/h3&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Route: Hashable {
    case recipesSearch
    case recipeDetail(recipe: Recipe)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We don&amp;#x27;t explicitly need it to use &lt;code&gt;NavigationPath&lt;/code&gt;, but I personally find it really helpful to have explicitly defined navigation routes.&lt;/p&gt;&lt;h3&gt;2. Creating an &lt;code&gt;Observable&lt;/code&gt; router which holds the navigation path&lt;/h3&gt;&lt;p&gt;To be able to access the same navigation path from different views, we create a router as an &lt;code&gt;Observable&lt;/code&gt; which holds the reference to the navigation path and provides a utility function for navigation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Observable
class Router {
    var navigationPath = NavigationPath()

    func navigateTo(route: Route) {
        navigationPath.append(route)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. Passing in the router as an environment object to all subviews&lt;/h3&gt;&lt;p&gt;For a subview to be able to access and use our created router, we pass it in as an environment object:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct MainView: View {
    @State private var router = RecipesRouter()

    var body: some View {
        NavigationStack(path: $router.navigationPath) {
            RecipesFeedView()
                .environment(router)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. Defining a navigation destination for possible routes&lt;/h3&gt;&lt;p&gt;As a next step, we define a &lt;code&gt;navigationDestination&lt;/code&gt; for each of our routes by providing views a specific route should navigate to:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;RecipesFeedView()
    .environment(router)
    .navigationDestination(for: RecipesRouter.Route.self) { route in
        switch route {
        case .recipesSearch:
            RecipesSearchView()
                .environment(router)
        case .recipeDetail(let recipe):
            RecipeDetailView(recipe: recipe)
                .environment(router)
        }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5. Using router where needed&lt;/h3&gt;&lt;p&gt;Finally, we can use the router for navigation anywhere we need by accessing it tough the &lt;code&gt;@Environment&lt;/code&gt; property wrapper:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct RecipesFeedView: View {
    @Environment(RecipesRouter.self) var router

    var body: some View {
        ...
        Button(action: {
            router.navigateTo(route: .recipesSearch)
        }, label: {
            Image(systemName: &amp;quot;magnifyingglass&amp;quot;)
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>navigation</category><category>design patterns</category><category>architecture</category><category>swiftui</category></item><item><title><![CDATA[Objective-C basics for Swift developers]]></title><description><![CDATA[Quick reference of key Objective-C concepts.]]></description><link>https://tanaschita.com/objective-c-for-swift-developers</link><guid isPermaLink="false">https://tanaschita.com/objective-c-for-swift-developers</guid><pubDate>Sun, 02 Jun 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Objective-C, the primary language for iOS development before the release of Swift in 2014, remains an integral part of many legacy projects and Apple frameworks.&lt;/p&gt;&lt;p&gt;For Swift developers, understanding the basics of Objective-C can be valuable, especially when working on older codebases or integrating with libraries that haven’t been updated to Swift.&lt;/p&gt;&lt;p&gt;This guide will help Swift developers get acquainted with Objective-C, highlighting key concepts and differences between the two languages.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Variables&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;When defining variables, Swift offers the &lt;code&gt;let&lt;/code&gt; keyword for constants and the &lt;code&gt;var&lt;/code&gt; keyword for variables. Swift offers type inference and optional data types.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let name = &amp;quot;Swifty&amp;quot; // declaring and assigning a constant

var age: Int? // declaring an optional Int variable
age = 7 // reassigning the variable
age = nil // nullifying the variable

var year = 2014 // the compiler automatically infers the `Int` type

let fruits = [&amp;quot;apple&amp;quot;, &amp;quot;orange&amp;quot;] // desclaring and assigning an array of strings
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Objective-C does not support type inference, we always have to explicitly specify the variable&amp;#x27;s type. It includes basic types like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;char&lt;/code&gt; and object types like &lt;code&gt;NSString&lt;/code&gt;, &lt;code&gt;NSNumber&lt;/code&gt; etc.&lt;/p&gt;&lt;p&gt;Unlike Swift, Objective-C does not have a separate optional type; all object types can inherently be nullified.&lt;/p&gt;&lt;p&gt;The same example in Objective-C looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSString const *name = @&amp;quot;Obji&amp;quot;; // declaring and assigning a constant

NSNumber *age; // declaring a variable
age = 7; // reassigning the variable
age = nil; // nullifying the variable

NSArray&amp;lt;NSString*&amp;gt; *fruits = @[@&amp;quot;apple&amp;quot;, @&amp;quot;orange&amp;quot;]; // desclaring and assigning an array of strings
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s go through some key points of the above example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Explicit Type Declaration&lt;/strong&gt;: Every variable must have its type explicitly declared.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pointers&lt;/strong&gt;: Object types are declared as pointers - which is indicated by &lt;code&gt;*&lt;/code&gt;. This is conceptually similar to reference types in Swift but syntactically different.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Optionals&lt;/strong&gt;: Swift’s optionals provide a clear way to handle &lt;code&gt;nil&lt;/code&gt; values, whereas in Objective-C, any object can be set to &lt;code&gt;nil&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Constants&lt;/strong&gt;: &lt;code&gt;const&lt;/code&gt; is used to declare constants.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Array syntax&lt;/strong&gt;: The &lt;code&gt;@&lt;/code&gt; symbol is used before array literals.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;String syntax&lt;/strong&gt;: String literals are prefixed with &lt;code&gt;@&lt;/code&gt; to differentiate them from C-style strings.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Properties&lt;/h3&gt;&lt;p&gt;In both Swift and Objective-C, properties are variables that are declared as part of a type. The primary difference lies in the syntax and additional attributes applied for access control and memory management.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Swift, properties are commonly used in classes, structures, and enumerations. By default, properties are strong and have an internal access level unless otherwise specified.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class Child {
    private let firstName: String // declaring a private constant property
    private(set) var lastName: String // declaring a read-only property
    var age: Int // declaring an internal property
    weak public var parent: Parent? // declaring a weak public property
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Objective-C, properties are used only in classes and are declared using the &lt;code&gt;@property&lt;/code&gt; keyword followed by comma-separated attributes specifying memory management and access control.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;@property NSString *firstName; // declaring a strong property
@property (readonly) NSString *lastName; // declaring a read-only property
@property (weak) Parent *parent; // declaring a read-only property
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The visibility of Objective-C properties is not achieved with attributes but with where we place them in our class declarations. We will look at that later in the classes section.&lt;/p&gt;&lt;h3&gt;Methods&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Swift, methods can be defined on classes, structures, and enumerations. Swift methods support various features like default parameters, multiple return values and concise syntax.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// method declaration with no return type and no parameters
func emptyCard()

// method declaration with two parameters and a return value
func search(_ searchQuery: String, category: String) -&amp;gt; [Tea]

// default parameters
func walk(withSpeed speed: Int = 20)

// method implementation
func search(_ searchQuery: String, category: String) -&amp;gt; [Tea] {
    return []
}

// calling a method
let results = teaShop.search(&amp;quot;camomile&amp;quot;, category: &amp;quot;herbs&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Objective-C, only classes can define methods. Objective-C does not support default parameters, making method overloading a common pattern for similar functionality with different parameters.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;// method declaration with no return type and no parameters
// `void` represents the absence of a type.
- (void)emptyCard;

// method declaration with two parameters and a return value
- (NSArray&amp;lt;Tea *&amp;gt; *)search:(NSString *)searchQuery category:(NSString *)category

// implementation of a method with two parameters and a return value
- (NSArray&amp;lt;Tea *&amp;gt; *)search:(NSString *)searchQuery category:(NSString *)category {
    return [];
}

// calling a method
NSArray&amp;lt;NSString *&amp;gt; *results = [teaShop search:@&amp;quot;camomile&amp;quot; category:@&amp;quot;herbs&amp;quot;];
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown above, methods in Objective-C have the &lt;code&gt;- (returnType)methodName:(paramType)paramName&lt;/code&gt; syntax. To call a method, we use square brackets, with parameters specified within.&lt;/p&gt;&lt;h3&gt;Classes &amp;amp; structs&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Swift, we distinguish between classes, which are reference types, and structs, which are value types.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Foundation

class TeaShop {
    // Public &amp;amp; private properties &amp;amp; methods
}

struct Tea {
    // Public &amp;amp; private properties &amp;amp; methods
}

// Using a class
let shop = TeaShop()

// Using a struct
let tea = Tea()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Objective-C, classes are defined using two separate files: the header file (&lt;code&gt;.h&lt;/code&gt;) and the implementation file (&lt;code&gt;.m&lt;/code&gt;). All classes are derived from the base class &lt;code&gt;NSObject&lt;/code&gt;, which provides essential methods for memory allocation and initialization.&lt;/p&gt;&lt;p&gt;&lt;code&gt;TeaShop.h&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;@import Foundation;

@interface TeaShop : NSObject
// Public properties &amp;amp; method definitions
@end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;TeaShop.m&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;#import &amp;quot;TeaShop.h&amp;quot;

@interface TeaShop ()
// private properties &amp;amp; method definitions
@end

@implementation TeaShop {
// Private instance variables
}
// Methods implementation
@end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Creating a new object in Objective-C is a two-step process. First, we allocate memory for the object and then initialize it.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;TeaShop *shop = [[TeaShop alloc] init];

// Or the shorter version, that will call `alloc` and `init` for us
TeaShop *shop = [[TeaShop new];
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Structs in Objective-C are similar to C structs and are less commonly used compared to classes. Objective-C, being a superset of C, allows the use of C constructs.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;// Declaration of a struct
struct Position
{
   int x;
   int y;
};

// Usage of a struct
struct Position position;
position.x = 5;
position.y = 10;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Enums&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Like structs, Swift enums are first-class types which support features like associated types, computed properties, custom initializers and more.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;enum TeeCategory {
    case herbal
    case green
    case fruits
}

let category = TeeCategory.green
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Objective-C, enums are less flexible compared to Swift enums. They are essentially C-style enums, defined using the &lt;code&gt;typedef&lt;/code&gt; keyword and &lt;code&gt;NS_ENUM&lt;/code&gt; macro for better type safety and integration with Objective-C.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;typedef NS_ENUM(NSInteger, TeeCategory) {
    TeeCategoryHerbal,
    TeeCategoryGreen,
    TeeCategoryFruits
} TeeCategory;

// Using an enum
TeeCategory category = TeeCategoryFruits;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Extensions&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Swift, extensions are a powerful way to add new functionality to existing classes, structures, enums, or protocols without modifying their original implementation. Extensions can add computed properties, methods, initializers, and conform to protocols.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension String {
    var asURL: URL? {
        URL(string: self)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;em&gt;Objective-C&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;In Objective-C, categories are used to add new functionality to existing classes. Categories, like classes, consist of a header file and an implementation file.&lt;/p&gt;&lt;p&gt;&lt;code&gt;NSString+URL.h&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@interface NSString (URL)

-(NSURL*)toURL;

@end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;NSString+URL.m&lt;/code&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#import &amp;quot;NSString+URL.h&amp;quot;

@implementation NSString (URL)

-(NSURL*)toURL {
    return [[NSURL alloc] initWithString:self];
}

@end
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>objective-c</category><category>swift</category><category>basics</category></item><item><title><![CDATA[How to create custom @Environment values in SwiftUI]]></title><description><![CDATA[Learn how to share data between SwiftUI views without passing the data explicitly.]]></description><link>https://tanaschita.com/swiftui-custom-environment-values</link><guid isPermaLink="false">https://tanaschita.com/swiftui-custom-environment-values</guid><pubDate>Sun, 26 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;code&gt;@Environment&lt;/code&gt; property wrapper in SwiftUI allows us to share data between views without explicitly passing the data from view to view.&lt;/p&gt;&lt;p&gt;While SwiftUI provides many built-in environment values, there are times when we need to create custom ones to suit our specific needs.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and see how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;To create a custom &lt;code&gt;@Environment&lt;/code&gt; value, we need to:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Declare a new &lt;code&gt;EnvironmentKey&lt;/code&gt; type.&lt;/li&gt;&lt;li&gt;Extend the &lt;code&gt;EnvironmentValues&lt;/code&gt; structure to include a property for our new custom key.&lt;/li&gt;&lt;li&gt;Use the &lt;code&gt;@Environment&lt;/code&gt; property wrapper to inject the custom value into our views.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;p&gt;As an example, we&amp;#x27;ll create a custom environment value to manage the theme of our iOS app. The theme includes properties like colors and font sizes that can be accessed throughout the app.&lt;/p&gt;&lt;h3&gt;1. Declaring a new &lt;code&gt;EnvironmentKey&lt;/code&gt; type&lt;/h3&gt;&lt;p&gt;At first, we create a new type that conforms to the &lt;code&gt;EnvironmentKey&lt;/code&gt; protocol. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private struct ThemeEnvironmentKey: EnvironmentKey {
    static let defaultValue: Theme = Theme.default
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. Extending the &lt;code&gt;EnvironmentValues&lt;/code&gt; structure&lt;/h3&gt;&lt;p&gt;Next, we extend the &lt;code&gt;EnvironmentValues&lt;/code&gt; structure to include a property for our custom environment key. This makes it accessible via the environment.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;extension EnvironmentValues {
    var theme: Theme {
        get { self[ThemeEnvironmentKey.self] }
        set { self[ThemeEnvironmentKey.self] = newValue }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3. Using the &lt;code&gt;@Environment&lt;/code&gt; property wrapper&lt;/h3&gt;&lt;p&gt;Now that we have defined a custom environment value, we can use it in our views with the &lt;code&gt;@Environment&lt;/code&gt; property wrapper.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;// Setting the environment value.
LoginView()
    .environment(\.theme, Theme.default)

// Accessing the environment value.
struct LoginView: View {
    @Environment(\.theme) var theme: Theme

    var body: some View {
        Text(&amp;quot;Hello&amp;quot;)
            .foregroundColor(theme.primary)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of a &lt;code&gt;String&lt;/code&gt;, we can of course use any other type for the environment value.&lt;/p&gt;</content:encoded><category>property wrappers</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Combine essentials in Swift - a quick guide]]></title><description><![CDATA[Learn basic Combine concepts and terms.]]></description><link>https://tanaschita.com/combine-essentials</link><guid isPermaLink="false">https://tanaschita.com/combine-essentials</guid><pubDate>Sun, 19 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple&amp;#x27;s Combine framework provides a native way to write functional reactive code in our iOS applications. In this article, we&amp;#x27;ll go through basic Combine concepts and terms.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Overview&lt;/h3&gt;&lt;p&gt;The core components of Combine are &lt;strong&gt;publishers&lt;/strong&gt; and &lt;strong&gt;subscribers&lt;/strong&gt;. Publishers emit values or errors over time, while subscribers receive and handle these emissions.&lt;/p&gt;&lt;p&gt;In addition, Combine provides a rich set of &lt;strong&gt;operators&lt;/strong&gt; that allow for transforming, filtering, and combining the emitted values.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at these main components in more detail with an example.&lt;/p&gt;&lt;h3&gt;Publishers&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s say, we are building an app that presents articles which can receive hearts. We want to be able to change the count of hearts for an article and also to notify subscribers about this change.&lt;/p&gt;&lt;p&gt;As a first step, we&amp;#x27;ll define a publisher which can send new values, in our case the new heart count.&lt;/p&gt;&lt;p&gt;For that, we&amp;#x27;ll use a &lt;strong&gt;subject&lt;/strong&gt; which is a mutable publisher with the ability to send new values after its initialization:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Article {

    // 3.
    var heartsCountPublisher: AnyPublisher&amp;lt;Int, Never&amp;gt; {
        heartsCountSubject.eraseToAnyPublisher()
    }

    // 1.
    private let heartsCountSubject: CurrentValueSubject&amp;lt;Int, Never&amp;gt;

    // 2.
    init(heartsCount: Int) {
        heartsCountSubject = CurrentValueSubject(heartsCount)
    }

    // 4.
    func addHeart() {
        heartsCountSubject.send(heartsCountSubject.value + 1)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s break down the code step by step:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;We define a &lt;strong&gt;subject&lt;/strong&gt; named &lt;code&gt;heartsCountSubject&lt;/code&gt; as a private variable of type &lt;code&gt;CurrentValueSubject&amp;lt;Int, Never&amp;gt;&lt;/code&gt;. We will use it to send new values to subscribers. Since a subject is a generic type we specify its &lt;code&gt;Output&lt;/code&gt; and &lt;code&gt;Failure&lt;/code&gt; type. The output type defines what kind of values the subject will send, in our case &lt;code&gt;Int&lt;/code&gt; values. Since our example doesn&amp;#x27;t involve errors, we use &lt;code&gt;Never&lt;/code&gt; as error type.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; there are two subject types available:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;CurrentValueSubject&lt;/code&gt;: This subject type maintains access to the current value.&lt;/li&gt;&lt;li&gt;&lt;code&gt;PassthroughSubject&lt;/code&gt;: This subject type simply passes values through and does not maintain access to the current value.&lt;/li&gt;&lt;/ul&gt;&lt;ol start=&quot;2&quot;&gt;&lt;li&gt;&lt;p&gt;We define an initializer that takes a &lt;code&gt;heartsCount&lt;/code&gt; parameter to set the initial value for &lt;code&gt;heartsCountSubject&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;We define a &lt;strong&gt;publisher&lt;/strong&gt; named &lt;code&gt;heartsCountPublisher&lt;/code&gt; of type &lt;code&gt;AnyPublisher&amp;lt;Int, Never&amp;gt;&lt;/code&gt;. It has the same &lt;code&gt;Output&lt;/code&gt; and &lt;code&gt;Failure&lt;/code&gt; type as our subject. Although we could make our subject public, using a publisher allows us to expose only the subscription functionality to the outside world, preventing external entities from sending new values.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;We define an &lt;code&gt;addHeart&lt;/code&gt; method for increasing the count of hearts. Here, we use our subject to send a new value. To be able to add a heart we access the subject&amp;#x27;s &lt;code&gt;value&lt;/code&gt; property to get the current value. That&amp;#x27;s the reason why we used a &lt;code&gt;CurrentValueSubject&lt;/code&gt; and not a &lt;code&gt;PassthroughSubject&lt;/code&gt; - only a &lt;code&gt;CurrentValueSubject&lt;/code&gt; has access to its current value.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Subscribers&lt;/h3&gt;&lt;p&gt;A publisher can have one or more subscribers. These subscribers request values and receive them when they are published.&lt;/p&gt;&lt;p&gt;In our example, we can use the &lt;code&gt;Article&lt;/code&gt; struct and subscribe to its hearts count as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// 1.
let article = Article(heartsCount: 5)

// 2.
let heartsCountSubscriber = article.heartsCountPublisher
    .sink { value in
        print(value)
    }

// 3.
article.addHeart()
article.addHeart()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s go through this code step by step.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;We create an &lt;code&gt;Article&lt;/code&gt; instance with an initial hearts count of 5.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;We create a &lt;strong&gt;subscriber&lt;/strong&gt; named &lt;code&gt;heartsCountSubscriber&lt;/code&gt; that listens for updates on the hearts count. The subscriber uses the publisher&amp;#x27;s &lt;code&gt;sink(receiveValue:)&lt;/code&gt; method to subscribe for updates. Now, every time the hearts count changes, the closure is called, printing the new value.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;We increase the count of hearts by calling &lt;code&gt;addHeart()&lt;/code&gt; twice.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Therefore, the code produces the following output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;5
6
7
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we create a new subscriber, the publisher returns an object that conforms to the &lt;code&gt;Cancellable&lt;/code&gt; protocol. This allows us to cancel receiving new values by calling the &lt;code&gt;cancel()&lt;/code&gt; method on the subscriber.&lt;/p&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;article.addHeart()
heartsCountSubscriber.cancel()
article.addHeart()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the code above, the last increment is not printed because the subscriber has been canceled.&lt;/p&gt;&lt;h3&gt;Operators&lt;/h3&gt;&lt;p&gt;Operators modify values sent from publishers, allowing us to transform, filter, and combine data streams.&lt;/p&gt;&lt;p&gt;In our example, we can use operators to modify received values before executing the print closure. For instance, to print a more descriptive message, we can use the &lt;code&gt;map&lt;/code&gt; operator to convert &lt;code&gt;Int&lt;/code&gt; values to &lt;code&gt;String&lt;/code&gt; values:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let heartsCountSubscriber = article.heartsCountPublisher
    .map { &amp;quot;\($0) hearts&amp;quot; }
    .sink { print($0) }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, our output looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;5 hearts
6 hearts
7 hearts
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can chain multiple operators to build complex data processing pipelines. Let&amp;#x27;s explore a more detailed example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let publisher1 = PassthroughSubject&amp;lt;[Int], Never&amp;gt;()
let publisher2 = PassthroughSubject&amp;lt;[Int], Never&amp;gt;()

publisher1
    .merge(with: publisher2) // 1.
    .flatMap { Publishers.Sequence(sequence: $0) } // 2.
    .filter { $0 % 2 == 0 } // 3.
    .dropFirst() // 4.
    .collect() // 5.
    .sink { print($0) }

publisher1.send([1, 3])
publisher2.send([6, 10])
publisher2.send([4, 19, 8])
publisher1.send(completion: .finished)
publisher2.send(completion: .finished)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While this example may not perform a useful task, it demonstrates how operators work and how they can be combined. Try to predict the output of each operator before checking the explanation below.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;merge&lt;/strong&gt;: Combines elements from &lt;code&gt;publisher1&lt;/code&gt; and &lt;code&gt;publisher2&lt;/code&gt;. Output: &lt;code&gt;[1, 3] [6, 10] [4, 19, 8]&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;flatMap&lt;/strong&gt;: Flattens the array into a sequence of &lt;code&gt;Int&lt;/code&gt; values. Output: &lt;code&gt;1, 3, 6, 10, 4, 19, 8&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;filter&lt;/strong&gt;: Keeps only even values in our example. Output: &lt;code&gt;6, 10, 4, 8&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;dropFirst&lt;/strong&gt;: Drops the first element. Output: &lt;code&gt;10, 4, 8&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;collect&lt;/strong&gt;: Collects all received items into an array. Output: &lt;code&gt;[10, 4, 8]&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To explore more operators, visit the &lt;a href=&quot;https://developer.apple.com/documentation/combine/publisher&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;official Publisher documentation&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Memory management in Combine&lt;/h3&gt;&lt;p&gt;In Combine, memory management is crucial to prevent memory leaks and retain cycles. The lifecycle of a subscriber is linked to the lifecycle of the retaining object. When this object is deallocated, the &lt;code&gt;cancel&lt;/code&gt; method is automatically called on the subscriber, releasing it as well.&lt;/p&gt;&lt;p&gt;To avoid creating retain cycles, especially when using &lt;code&gt;self&lt;/code&gt; within a &lt;code&gt;sink&lt;/code&gt; closure, we need to manage memory carefully. One common approach is to use the &lt;code&gt;AnyCancellable&lt;/code&gt; type. When we subscribe to a publisher, it returns an &lt;code&gt;AnyCancellable&lt;/code&gt; instance which we can store and manage appropriately. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()

publisher.sink {
   // ...
}
.store(in: &amp;amp;cancellables)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, we:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use &lt;code&gt;[weak self]&lt;/code&gt; to avoid a retain cycle, ensuring that self is not strongly captured by the closure.&lt;/li&gt;&lt;li&gt;Store the &lt;code&gt;AnyCancellable&lt;/code&gt; instance returned by &lt;code&gt;sink&lt;/code&gt; in a &lt;code&gt;Set&lt;/code&gt; called &lt;code&gt;cancellables&lt;/code&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;When the object holding the &lt;code&gt;cancellables&lt;/code&gt; set is deallocated, the &lt;code&gt;AnyCancellable&lt;/code&gt; instances in the set are automatically cancelled. These steps prevent memory leaks and ensure that subscriptions are properly terminated.&lt;/p&gt;</content:encoded><category>combine</category><category>reactive programming</category><category>swift</category></item><item><title><![CDATA[How to combine SwiftUI and UIKit views in iOS]]></title><description><![CDATA[Learn how to bridge SwiftUI and UIKit views.]]></description><link>https://tanaschita.com/swiftui-uikit</link><guid isPermaLink="false">https://tanaschita.com/swiftui-uikit</guid><pubDate>Sun, 12 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With SwiftUI pushing UIKit into the background since 2019, UIKit may still be part of existing iOS applications.&lt;/p&gt;&lt;p&gt;To combine those two worlds, Apple provides tools to place UIKit views and view controllers inside SwiftUI views, and vice versa.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using UIKit views inside SwiftUI views&lt;/h3&gt;&lt;p&gt;To use a UIKit view in a SwiftUI view, we wrap the UIKit view in a SwiftUI view that conforms to the &lt;code&gt;UIViewRepresentable&lt;/code&gt; protocol.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct Label: UIViewRepresentable {
    @Binding var title: String

    func makeUIView(context: Context) -&amp;gt; UILabel {
        let label = UILabel()
        return label
    }

    func updateUIView(_ label: UILabel, context: Context) {
        label.text = title
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When implementing the &lt;code&gt;UIViewRepresentable&lt;/code&gt; protocol, we need to implement two methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;makeUIView&lt;/code&gt; where we create and return our UIKit view.&lt;/li&gt;&lt;li&gt;&lt;code&gt;updateUIView&lt;/code&gt; which gets called from SwiftUI every time state changes happen, so we can update our UIKit view accordingly.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Using UIKit view controllers in SwiftUI views&lt;/h3&gt;&lt;p&gt;In SwiftUI, there is no concept of a UIKit view controller - everything is a view.&lt;/p&gt;&lt;p&gt;To embed a view controller in a SwiftUI view, we can use the same approach as described above. With the only difference, that this time, the SwiftUI view conforms to the &lt;code&gt;UIViewControllerRepresentable&lt;/code&gt; protocol:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeView: UIViewControllerRepresentable {
    @Binding var title: String

    func makeUIViewController(context: Context) -&amp;gt; SomeViewController {
        let vc = SomeViewController()
        return vc
    }

    func updateUIViewController(_ uiViewController: SomeViewController, context: Context) {
        uiViewController.title = title
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, it has equivalent methods as the &lt;code&gt;UIViewRepresentable&lt;/code&gt; protocol.&lt;/p&gt;&lt;h3&gt;Using SwiftUI views inside UIKit views&lt;/h3&gt;&lt;p&gt;For using SwiftUI views inside UIKit, Apple provides a class named &lt;code&gt;UIHostingViewController&lt;/code&gt;. We simply initialize a &lt;code&gt;UIHostingController&lt;/code&gt; with the SwiftUI view and then use it as usual.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let vc = UIHostingController(rootView: Text(&amp;quot;Hi&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swiftui</category><category>uikit</category><category>swift</category></item><item><title><![CDATA[How to localize plurals with Localizable.stringsdict files in iOS]]></title><description><![CDATA[Learn to support plurals for multiple languages.]]></description><link>https://tanaschita.com/ios-plurals-localization-strictdict</link><guid isPermaLink="false">https://tanaschita.com/ios-plurals-localization-strictdict</guid><pubDate>Sun, 05 May 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, we&amp;#x27;ll look at how to localize plurals with &lt;code&gt;Localizable.stringsdict&lt;/code&gt; files. If you are using String Catalogs, check out this &lt;a target=&quot;_blank&quot; href=&quot;/20230710-pluralization-with-string-catalogs/&quot;&gt;article&lt;/a&gt; instead.&lt;/p&gt;&lt;p&gt;If you are already familiar with localizing plurals in iOS and you are just looking to remember the specifics, you can directly scroll down to the cheat sheet at the end of the article.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Every time we need to localize texts like &lt;code&gt;My dog ate 2 carrots&lt;/code&gt; where the carrot count is dynamic, one localization string will not be enough. For example, localizing with &lt;code&gt;My dog ate %i carrots&lt;/code&gt; would produce &lt;code&gt;My dog ate 1 carrots&lt;/code&gt; when passing in &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The first solution that may come up could be to create another localization string for one carrot and adding some logic such as:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;if carrotCount == 1 {
    return NSLocalizedString(&amp;quot;dog_eating_carrots_one&amp;quot;)
} else {
    return String(format: NSLocalizedString(&amp;quot;dog_eating_carrots_multiple&amp;quot;), carrotCount)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The problem with the solution above is that different languages vary in how they handle plurals. For example, in Russian, we would need to handle more cases. Plural rules for the word &lt;em&gt;dog&lt;/em&gt; look like this:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;собака&lt;/code&gt; for &lt;code&gt;1, 21, 31, 41, 51, 61&lt;/code&gt; etc. Examples: &lt;code&gt;1 собака, 21 собака&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;собаки&lt;/code&gt; for &lt;code&gt;2-4, 22-24, 32-34&lt;/code&gt; etc. Examples: &lt;code&gt;2 собаки, 22 собаки&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;собак&lt;/code&gt; for &lt;code&gt;5-20, 25-30, 35-40&lt;/code&gt; etc. Examples: &lt;code&gt;5 собак, 20 собак&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It would be complicated to handle this logic in code. Instead, we can use a &lt;code&gt;Localizable.strigsdict&lt;/code&gt; file to solve it.&lt;/p&gt;&lt;p&gt;Unlike a &lt;code&gt;Localizable.strings&lt;/code&gt; file, a &lt;code&gt;Localizable.strigsdict&lt;/code&gt; file provides additional features to work with different languages, for example to define plural rules. The &lt;code&gt;Localizable.strigsdict&lt;/code&gt; is able to interpret the arguments you pass in and select the right localized string based on it.&lt;/p&gt;&lt;h3&gt;Creating and localizing a &lt;code&gt;Localizable.stringdict&lt;/code&gt; file&lt;/h3&gt;&lt;p&gt;To create a &lt;code&gt;Localizable.stringdict&lt;/code&gt; file, we can select a &lt;code&gt;.stringdict&lt;/code&gt; template from Xcode&amp;#x27;s dialog when creating a new file. The localization process is exactly the same as for &lt;code&gt;Localizable.strings&lt;/code&gt; files.&lt;/p&gt;&lt;h3&gt;Adding plural configuration&lt;/h3&gt;&lt;p&gt;Now, we can add the plural configuration for a specific language. We can use the template as a starting point:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1100px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/790e363fe2ae7f8bc955208df247164b/140de/ios_stringdict_template.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:44.800000000000004%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABEElEQVQoz22SCY6DMAxFuf8JkRD7IvalLS1QwNXzKKjQsWQSFPv72Ynl+b4kSSJ1XUtVVdK2rTRNczj/rutKnufSdZ04jiNZlv3EGbeKohDP8ySKIgnDUJPWdT18WRZ5Pp/yeDz0v+97Fdv3XbZtO8XiFp/b7aaicRxLmqaaYAwh6HFEyrLUjlgphCFs3Hq/3/J6vcS2bRUdhkGTWSk0TZMKQE9xCgZBIHTGiO73+0GrgtM0axUOfd9XStpnTyIFSWY+GFTjOCo5YuxPhMwIowUCzNwQMmdQURCD5monwXn+I4SA9kwAiSaZGzaC10v4FjsR0haE14pXwqvAj6AhRJAnYSjMk4CSM3PL/1F9C34Auuy2uqJU0gEAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;The .stringdict template&quot; title=&quot;The .stringdict template&quot; src=&quot;/static/790e363fe2ae7f8bc955208df247164b/140de/ios_stringdict_template.png&quot; srcSet=&quot;/static/790e363fe2ae7f8bc955208df247164b/12d18/ios_stringdict_template.png 375w,/static/790e363fe2ae7f8bc955208df247164b/44507/ios_stringdict_template.png 750w,/static/790e363fe2ae7f8bc955208df247164b/140de/ios_stringdict_template.png 1100w&quot; sizes=&quot;(max-width: 1100px) 100vw, 1100px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;The .stringdict template&lt;/div&gt;&lt;p&gt;The configuration for our example could look as follows:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1110px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/65107e9546ee0ca4e0991ecb38a04c3b/7d6d7/ios_stringdict_ready.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:34.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABV0lEQVQoz1WM3WrbMABG/f6vNNguxi42Bk2hydo4syvb8X+UOJYtR7J0RlTWrYKDpI/DiZ5eEkTZkRYNourJaomoToG8luzTgu1ecOwG4teSzTb+4Pwlq2Qg+vIg+PQz5fOD4NuvjsfCsGtg18JGTPyIJV93Pd8PA4/FLTibfAnOtvFvbgNPlQtEfjWINCETKUUu6NuG4XziOpwBj54V2WuCGgesWairI+dTx1n2wbvIPry9s4Ajcs5RVRXPzy8IIThJSdf3dF3P7WaY55n4cGAYrsxa8ztJKMuSpm2p65pRqeCtq+PeigC01iRJQlEUZFkWwve/lBKtl7BZa4N33+5xpRTjOGKMwXtYnQuE4Dwpuq7FrSvWGlZrseYG3qHniTRN3rdjkTNeB/4dH7xQ9Z5IG+gvE+LYMBuYFvfObDxymInTnGEyXNTCPsnIqw61rCi9vt3/8Qc4cBDegCus3wAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;A .stringdict configuration example&quot; title=&quot;A .stringdict configuration example&quot; src=&quot;/static/65107e9546ee0ca4e0991ecb38a04c3b/7d6d7/ios_stringdict_ready.png&quot; srcSet=&quot;/static/65107e9546ee0ca4e0991ecb38a04c3b/12d18/ios_stringdict_ready.png 375w,/static/65107e9546ee0ca4e0991ecb38a04c3b/44507/ios_stringdict_ready.png 750w,/static/65107e9546ee0ca4e0991ecb38a04c3b/7d6d7/ios_stringdict_ready.png 1110w&quot; sizes=&quot;(max-width: 1110px) 100vw, 1110px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;A .stringdict configuration example&lt;/div&gt;&lt;p&gt;Let&amp;#x27;s go through it step by step.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Localized String Key&lt;/strong&gt; — is replaced with the key that we will use when calling &lt;code&gt;NSLocalizedString&lt;/code&gt;. In our example the key is &lt;code&gt;dog_eating_carrots&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;NSStringLocalizedFormatKey&lt;/strong&gt; - the localized text with parameters. In our case it&amp;#x27;s &lt;code&gt;My dog ate %#@carrotsCount@.&lt;/code&gt;. It has the following structure:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;every variable is be preceded by &lt;code&gt;%#@&lt;/code&gt; and followed by &lt;code&gt;@&lt;/code&gt;&lt;/li&gt;&lt;li&gt;we only need to define variables for parameters where plural rules should be applied&lt;/li&gt;&lt;li&gt;in case we need to pass in additional arguments, we can simply use the string format specifiers we already know such as &lt;code&gt;%@&lt;/code&gt;, &lt;code&gt;%d&lt;/code&gt; etc. For example, to pass in the dog&amp;#x27;s name, our format string would look as follows: &lt;code&gt;My dog %@ ate %#@carrotsCount@.&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Variable&lt;/strong&gt; — the rules dictionary that applies to a specific variable. We create one for every variable we defined in &lt;code&gt;NSStringLocalizedFormatKey&lt;/code&gt;. In our example, it&amp;#x27;s one variable with the name &lt;code&gt;carrotsCount&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;NSStringFormatSpecTypeKey&lt;/strong&gt; — parameter processing rule. It&amp;#x27;s already set to &lt;code&gt;NSStringPluralRuleType&lt;/code&gt; so we can leave it as is.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;NSStringFormatValueTypeKey&lt;/strong&gt; — specifies the format of the parameter, for example &lt;code&gt;d&lt;/code&gt; for &lt;code&gt;integer&lt;/code&gt; or &lt;code&gt;f&lt;/code&gt; for &lt;code&gt;double&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLDR Language Plural Rules&lt;/strong&gt; — we can choose different plural rules like &lt;code&gt;zero&lt;/code&gt;, &lt;code&gt;one&lt;/code&gt;, &lt;code&gt;two&lt;/code&gt;, &lt;code&gt;few&lt;/code&gt;, &lt;code&gt;many&lt;/code&gt; and &lt;code&gt;other&lt;/code&gt;. For some languages like English we only need to configure two rules, &lt;code&gt;one&lt;/code&gt; and &lt;code&gt;other&lt;/code&gt;. Other languages have only a single plural rule and some languages have more than two.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Switching between property list and source code&lt;/h3&gt;&lt;p&gt;Xcode provides a user interface to work with property list files. Depending on your preference, you can directly work with source code instead. To switch, right-click on the &lt;code&gt;Localizable.stringdict&lt;/code&gt; file and choose &lt;code&gt;Open as -&amp;gt; Source Code&lt;/code&gt;. Switching back works the same way with &lt;code&gt;Open as -&amp;gt; Property List&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The source code version of our example above looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;!DOCTYPE plist PUBLIC &amp;quot;-//Apple//DTD PLIST 1.0//EN&amp;quot; &amp;quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;quot;&amp;gt;
&amp;lt;plist version=&amp;quot;1.0&amp;quot;&amp;gt;
&amp;lt;dict&amp;gt;
    &amp;lt;key&amp;gt;dog_eating_carrots&amp;lt;/key&amp;gt;
    &amp;lt;dict&amp;gt;
        &amp;lt;key&amp;gt;NSStringLocalizedFormatKey&amp;lt;/key&amp;gt;
        &amp;lt;string&amp;gt;My dog ate %#@carrotsCount@.&amp;lt;/string&amp;gt;
        &amp;lt;key&amp;gt;carrotsCount&amp;lt;/key&amp;gt;
        &amp;lt;dict&amp;gt;
            &amp;lt;key&amp;gt;NSStringFormatSpecTypeKey&amp;lt;/key&amp;gt;
            &amp;lt;string&amp;gt;NSStringPluralRuleType&amp;lt;/string&amp;gt;
            &amp;lt;key&amp;gt;NSStringFormatValueTypeKey&amp;lt;/key&amp;gt;
            &amp;lt;string&amp;gt;d&amp;lt;/string&amp;gt;
            &amp;lt;key&amp;gt;zero&amp;lt;/key&amp;gt;
            &amp;lt;string&amp;gt;no carrots&amp;lt;/string&amp;gt;
            &amp;lt;key&amp;gt;one&amp;lt;/key&amp;gt;
            &amp;lt;string&amp;gt;1 carrot&amp;lt;/string&amp;gt;
            &amp;lt;key&amp;gt;other&amp;lt;/key&amp;gt;
            &amp;lt;string&amp;gt;%d carrots&amp;lt;/string&amp;gt;
        &amp;lt;/dict&amp;gt;
    &amp;lt;/dict&amp;gt;
&amp;lt;/dict&amp;gt;
&amp;lt;/plist&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Cheat sheet&lt;/h2&gt;&lt;p&gt;And that&amp;#x27;s basically it. Below is the promised cheat sheet which shows another example with two variables.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/45c66daedccb750abc4c4fe04ab04df6/9fabd/ios_stringdict_cheatsheet.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:50.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgElEQVQoz2WS6W7jMAyE8/5vtwt0sylSY93Wlmgd1GXdR6EGCArs4PtLcDjDi3WBUAaH2Mmx7XQnB6Fs2+Hjk6CyMdUQy//EVGOqF+v8+/sHIRRRaW04F4wxSkEIeZ7e+/Dk9CHl2vpofeTSU26XUjoiLssCACF47z3n3FrDOTfGuClrrbHO5fOUBF7vy9vyjwK3LlxyaaWUbdsIIQCgvmWtZYwZY3LOpZSSp0ZrBxy/fr+8/Lnt5DDWz+GUEiK21nrvzjlEVEpxIcYY/QdjDONOmAdpxqWQetqOMa7rCgCMMW2MlNI5J4WwhFQpK+KDhmgY3yjb94MLFVOZm2utWutapz1EaYxGRMaOUWuNsaXUUqopjVKkkH9v9+v1dV0/T58u39GnEHPvo/eRc51JlhZzaX3UNnLtD0obp08UOBfKujDTNsrfrve3ZQWqt50D5RIOA1wLLZVW2j5bfZBLf/Q0ew6+oLRcKNSOoeJqxqW1llpLY6TRZ4gptefwzyf5AlisOKcKZmc0AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Cheatsheet for localizing plurals in iOS&quot; title=&quot;Cheatsheet for localizing plurals in iOS&quot; src=&quot;/static/45c66daedccb750abc4c4fe04ab04df6/2e9ed/ios_stringdict_cheatsheet.png&quot; srcSet=&quot;/static/45c66daedccb750abc4c4fe04ab04df6/12d18/ios_stringdict_cheatsheet.png 375w,/static/45c66daedccb750abc4c4fe04ab04df6/44507/ios_stringdict_cheatsheet.png 750w,/static/45c66daedccb750abc4c4fe04ab04df6/2e9ed/ios_stringdict_cheatsheet.png 1500w,/static/45c66daedccb750abc4c4fe04ab04df6/9fabd/ios_stringdict_cheatsheet.png 2000w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Cheatsheet for localizing plurals in iOS&lt;/div&gt;</content:encoded><category>localization</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[How to solve problems with bitwise operators in Swift]]></title><description><![CDATA[Learn to use the power of bitwise operations and option sets in iOS.]]></description><link>https://tanaschita.com/swift-working-with-bits</link><guid isPermaLink="false">https://tanaschita.com/swift-working-with-bits</guid><pubDate>Sun, 28 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Knowing how to work with bitwise operators can become really useful when solving specific types of problems.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and look at an example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Recently, I had to solve the following task at work.&lt;/p&gt;&lt;p&gt;The server sends an integer value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;127&lt;/code&gt; that represents the state of a weekdays selection. When the server sends a &lt;code&gt;0&lt;/code&gt;, no checkbox should be selected. For a &lt;code&gt;127&lt;/code&gt;, all check boxes should be selected. The numbers in between represent all the other states. The task was to evaluate this value to be able to preselect the appropriate checkboxes.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:300px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/0f9b7793ce791c2b4ddd9c7705495ab0/37e03/swift-working-with-bits-weekdays_checkboxes.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:123.33333333333331%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAZABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAgEABf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHuooOmSqVRrj//xAAWEAEBAQAAAAAAAAAAAAAAAAABIBD/2gAIAQEAAQUCl1j/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ak//xAAdEAABBAIDAAAAAAAAAAAAAABBAAER4RAxIHGB/9oACAEBAAE/ISVHamA7+o1jakbxtfD/2gAMAwEAAgADAAAAEJD+DP/EABcRAAMBAAAAAAAAAAAAAAAAAAAQEWH/2gAIAQMBAT8QJr//xAAWEQADAAAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8QK//EACAQAQABAwMFAAAAAAAAAAAAAAERACFREEGRMYGh0fD/2gAIAQEAAT8QBse+ahnlS0gTMPdRsCZz0F9jzodqfcaInc5lb6PVr//Z&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Visualized check boxes&quot; title=&quot;Visualized check boxes&quot; src=&quot;/static/0f9b7793ce791c2b4ddd9c7705495ab0/37e03/swift-working-with-bits-weekdays_checkboxes.jpg&quot; srcSet=&quot;/static/0f9b7793ce791c2b4ddd9c7705495ab0/37e03/swift-working-with-bits-weekdays_checkboxes.jpg 300w&quot; sizes=&quot;(max-width: 300px) 100vw, 300px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Check boxes for weekday selection&lt;/div&gt;&lt;p&gt;The logic behind the integer value is simple when we imagine that each weekday is represented by it&amp;#x27;s bit value:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mon tue wed thu fri sat sun

0000000 - equals 0 - no weekday is selected
0000011 - equals 3 - sat sun are selected
1010101 - equals 85 - mon wed fri sun are selected
1111100 - equals 124 - mon tue wed thu fri are selected
1111111 - equals 127 - all days are selected
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this pattern, we are able to represent every state of the seven check boxes with numbers ranging from 0 to 127.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:300px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/2823917658fbec521eba4d9c2667f197/37e03/swift-working-with-bits-weekdays_checkboxes_85.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:120.66666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAYABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB7tmiBJoXIuf/xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAEFAh//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ah//xAAcEAACAQUBAAAAAAAAAAAAAAAAEUEBECHR4TH/2gAIAQEAAT8hnoqDT2Zc2mvpPLYddH//2gAMAwEAAgADAAAAEHA/A//EABYRAAMAAAAAAAAAAAAAAAAAAAAQEf/aAAgBAwEBPxB0/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAR/9oACAECAQE/EHD/xAAcEAACAgMBAQAAAAAAAAAAAAABEQAhMVGREHH/2gAIAQEAAT8QCpVmft2BgABAwtC9DxHJloagWK68WGG7todn/9k=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Checkboxes selection for value 85 (1010101)&quot; title=&quot;Checkboxes selection for value 85 (1010101)&quot; src=&quot;/static/2823917658fbec521eba4d9c2667f197/37e03/swift-working-with-bits-weekdays_checkboxes_85.jpg&quot; srcSet=&quot;/static/2823917658fbec521eba4d9c2667f197/37e03/swift-working-with-bits-weekdays_checkboxes_85.jpg 300w&quot; sizes=&quot;(max-width: 300px) 100vw, 300px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Checkboxes selection for value 85 (1010101)&lt;/div&gt;&lt;p&gt;Now, the question is:&lt;/p&gt;&lt;p&gt;&lt;em&gt;What is the best approach to convert the number send by the server into the correct checkboxes state?&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;The trivial solution&lt;/h3&gt;&lt;p&gt;The most trivial approach which might come to our mind could look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;switch weekdaysSelectionValue {
    case 0:
        monCheckbox.isChecked = false
        tueCheckbox.isChecked = false
        wedCheckbox.isChecked = false
        thuCheckbox.isChecked = false
        friCheckbox.isChecked = false
        satCheckbox.isChecked = false
        sunCheckbox.isChecked = false
    case 1:
        monCheckbox.isChecked = false
        tueCheckbox.isChecked = false
        wedCheckbox.isChecked = false
        thuCheckbox.isChecked = false
        friCheckbox.isChecked = false
        satCheckbox.isChecked = false
        sunCheckbox.isChecked = true
    ...
    case 127:
        monCheckbox.isChecked = true
        tueCheckbox.isChecked = true
        wedCheckbox.isChecked = true
        ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Having 127 switch cases is a lot of fun, but let&amp;#x27;s look into a more generic approach to solve this with bitwise operators.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;monCheckbox.isChecked = // get the bit on the 6th position &amp;amp; check if it is 0 or 1
tueCheckbox.isChecked = // get the bit on the 5th position &amp;amp; check if it is 0 or 1
wedCheckbox.isChecked = // get the bit on the 4th position &amp;amp; check if it is 0 or 1
thuCheckbox.isChecked = // get the bit on the 3th position &amp;amp; check if it is 0 or 1
friCheckbox.isChecked = // get the bit on the 2th position &amp;amp; check if it is 0 or 1
satCheckbox.isChecked = // get the bit on the 1th position &amp;amp; check if it is 0 or 1
sunCheckbox.isChecked = // get the bit on the 0th position &amp;amp; check if it is 0 or 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To implement the solution above, let&amp;#x27;s look into how to work with bits in Swift.&lt;/p&gt;&lt;h3&gt;Basics about bits in Swift&lt;/h3&gt;&lt;p&gt;A bit is the smallest piece of information that can be 1 or 0. Eight bits together are called a byte.&lt;/p&gt;&lt;p&gt;The unsigned &lt;code&gt;UInt8&lt;/code&gt; type in Swift is represented by 8 bits - which means we can represent 2⁸ = 256 different numbers with &lt;code&gt;UInt8&lt;/code&gt;. That&amp;#x27;s enough for our use case since we only need 127 numbers.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;UInt8

0 = 00000000 = 0 * 2^0
1 = 00000001 = 1 * 2^0
2 = 00000010 = 0 * 2^0 + 1 * 2^1
3 = 00000011 = 1 * 2^0 + 1 * 2^1
...
6 = 00000110 = 1 * 2^0 + 1 * 2^1 + 1 * 2^2
...
255 = 11111111 = 1 * 2^0 + 1 * 2^1 + 1 * 2^2 + 1 * 2^3 + 1 * 2^4 + 1 * 2^5 + 1 * 2^6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A signed &lt;code&gt;Int8&lt;/code&gt; type also represents 256 different numbers, but ranging from -128 to 127. The most left bit is used to signify whether the integer is positive or negative. So theoretically, we could also use an &lt;code&gt;Int8&lt;/code&gt; for our task.&lt;/p&gt;&lt;p&gt;In Swift, when defining a number, we can choose to use its bit representation:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let value1: UInt8 = 3
let value2: UInt8 = 0b00000011
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, &lt;code&gt;value1&lt;/code&gt; and &lt;code&gt;value2&lt;/code&gt; are the same values, they are just written down differently.&lt;/p&gt;&lt;h3&gt;Bitwise shifting and bitwise operators in Swift&lt;/h3&gt;&lt;p&gt;Additionally to basic operators, Swift offers advanced operators for bit manipulations, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0b00000011 | 0b00000101 // The bitwise OR operator produces 0b00000111
0b00000011 &amp;amp; 0b00000101 // The bitwise AND operator produces 0b00000001
0b00000010 &amp;lt;&amp;lt; 1 // The bitwise LEFT SHIFT operator &amp;lt;&amp;lt; produces 0b0000100
0b00000010 &amp;gt;&amp;gt; 1 // The bitwise RIGHT SHIFT operator &amp;gt;&amp;gt; produces 0b0000001
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;the &lt;strong&gt;OR&lt;/strong&gt; operator &lt;code&gt;|&lt;/code&gt; produces a &lt;code&gt;0&lt;/code&gt; only if both bits are a &lt;code&gt;0&lt;/code&gt;. Otherwise it produces a &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;the &lt;strong&gt;AND&lt;/strong&gt; operator &lt;code&gt;&amp;amp;&lt;/code&gt; produces a &lt;code&gt;1&lt;/code&gt; only if both bits are a &lt;code&gt;1&lt;/code&gt;. Otherwise it produces a &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;the &lt;strong&gt;LEFT SHIFT&lt;/strong&gt; operator &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; shifts every bit to the left by a certain number of places. Any bits that are moved beyond the bounds are discarded.&lt;/li&gt;&lt;li&gt;the &lt;strong&gt;RIGHT SHIFT&lt;/strong&gt; operator &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; does the same as the LEFT SHIFT operator in the other direction.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Solution with bitwise operators&lt;/h3&gt;&lt;p&gt;With the help of those operators, we now can do the following.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;monCheckbox.isChecked = ((serverValue &amp;amp; 0b01000000) &amp;gt;&amp;gt; 6) == 1
tueCheckbox.isChecked = ((serverValue &amp;amp; 0b00100000) &amp;gt;&amp;gt; 5) == 1
wedCheckbox.isChecked = ((serverValue &amp;amp; 0b00010000) &amp;gt;&amp;gt; 4) == 1
thuCheckbox.isChecked = ((serverValue &amp;amp; 0b00001000) &amp;gt;&amp;gt; 3) == 1
friCheckbox.isChecked = ((serverValue &amp;amp; 0b00000100) &amp;gt;&amp;gt; 2) == 1
satCheckbox.isChecked = ((serverValue &amp;amp; 0b00000010) &amp;gt;&amp;gt; 1) == 1
sunCheckbox.isChecked = ((serverValue &amp;amp; 0b00000001) &amp;gt;&amp;gt; 0) == 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What exactly is happening here? Let&amp;#x27;s pick one value to illustrate this.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1.&lt;/strong&gt; Imagine, we get a &lt;code&gt;85&lt;/code&gt; from the server. That is a &lt;code&gt;0b01010101&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; Now, we want to know if the Friday&amp;#x27;s checkbox is checked. For that, we nullify all bits instead of the one on position &lt;code&gt;2&lt;/code&gt; by using the AND operator.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0b01010101 &amp;amp; 0b00000100 // produces 0b000000100
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;3.&lt;/strong&gt; Next, we want to know if the value on the second position is a &lt;code&gt;1&lt;/code&gt; or a &lt;code&gt;0&lt;/code&gt;. For that, we shift &lt;code&gt;2&lt;/code&gt; positions to the right, so the value is now at position &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0b000000100 &amp;gt;&amp;gt; 2 // produces 0b000000001
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;4.&lt;/strong&gt; Finally, we can check if our produced value is a &lt;code&gt;0&lt;/code&gt; or a &lt;code&gt;1&lt;/code&gt; with the basic &lt;code&gt;==&lt;/code&gt; operator.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;0b000000001 == 1 // is true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Actually, we can even solve this without shifting. Instead of shifting to the right and checking if the value is &lt;code&gt;1&lt;/code&gt;, we can simply check if our value is not &lt;code&gt;0&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;friCheckbox.isChecked = serverValue &amp;amp; 0b00000100 != 0
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Solution with an OptionSet&lt;/h3&gt;&lt;p&gt;Now, since we understand how to solve this task with bitwise operators, there is an even simpler solution. Swift&amp;#x27;s standard library offers an &lt;code&gt;OptionSet&lt;/code&gt; type which represents bit set types.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct WeekdayOptions: OptionSet {
    let rawValue: Int

    static let sun = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 0)
    static let sat = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 1)
    static let fri = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 2)
    static let thu = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 3)
    static let wed = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 4)
    static let tue = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 5)
    static let mon = WeekdayOptions(rawValue: 1 &amp;lt;&amp;lt; 6)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After defining weekday options as an &lt;code&gt;OptionSet&lt;/code&gt; as shown above, we get all the logic behind it for free. To check if Monday is selected, we can simply use the &lt;code&gt;OptionSet&lt;/code&gt;&amp;#x27;s &lt;code&gt;contains&lt;/code&gt; method.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let weekdayOptions = WeekdayOptions(rawValue: serverValue)
monCheckbox.isSelected = weekdayOptions.contains(.mon)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though an &lt;code&gt;OptionSet&lt;/code&gt; makes it easy, knowing the bit operators and how it works behind the scenes makes its usage more clear.&lt;/p&gt;</content:encoded><category>swift</category><category>ios</category></item><item><title><![CDATA[GraphQL essentials for iOS development]]></title><description><![CDATA[Understand the concept behind GraphQL in context of iOS development.]]></description><link>https://tanaschita.com/graphql-essentials-for-ios</link><guid isPermaLink="false">https://tanaschita.com/graphql-essentials-for-ios</guid><pubDate>Sun, 21 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GraphQL is an API design paradigm which has emerged as an alternative to RESTful APIs. Compared to REST, which exposes fixed endpoints for different resources, GraphQL enables clients to query and retrieve only the data they need, in a single request.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll dive into the essentials of GraphQL for iOS development. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Understanding the difference between GraphQL and REST&lt;/h3&gt;&lt;p&gt;To get a first impression of how GraphQL works, let&amp;#x27;s look at an example and how it is solved with GraphQL compared to REST.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s say we&amp;#x27;re implementing the frontend of a dog treats shop displaying an overview of the orders a user made.&lt;/p&gt;&lt;p&gt;When working with a REST API, we would use multiple endpoints where each endpoint returns specific information, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;/orders&lt;/code&gt; to get all orders with basic information&lt;/li&gt;&lt;li&gt;&lt;code&gt;/order/[id]&lt;/code&gt; to get the details of a specific order&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It may happen that the information we get from the &lt;code&gt;/orders&lt;/code&gt; endpoint will not be enough for our frontend needs because we want to display some of the details directly in the orders overview. To achieve this with REST, we could:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Send an additional request for every order to load more details to the &lt;code&gt;/order/[id]&lt;/code&gt; endpoint.&lt;/li&gt;&lt;li&gt;Or ask the backend team to add more data to the &lt;code&gt;/orders&lt;/code&gt; endpoint.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Both ways are not ideal. With the first approach, we end up sending a lot of requests to the server. With the second approach, every time frontend requirements change, we may need to change the backend.&lt;/p&gt;&lt;p&gt;In comparison to REST, GraphQL APIs only expose a single endpoint. We can specify precisely what data we need. By using GraphQL, we define all needed detail fields in a GraphQL query:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  orders {
    id
    name
    price
    status
    supplier {
        id
        name
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When we send a request with this query to the server, we get exactly the data we defined, nothing more and nothing less.&lt;/p&gt;&lt;h3&gt;GraphQL as server-side runtime&lt;/h3&gt;&lt;p&gt;As an iOS developer, in most cases we won&amp;#x27;t need to know all the details on how GraphQL is implemented server-side wise.&lt;/p&gt;&lt;p&gt;An interesting thing to know is that GraphQL isn&amp;#x27;t tied to any specific framework, programming language or database. GraphQL acts more as a layer lying between an existing backend implementation and the client.&lt;/p&gt;&lt;p&gt;Many different programming languages support GraphQL, including Swift. For example, &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/GraphQLSwift/Graphiti&quot;&gt;Graphiti&lt;/a&gt; is a Swift library for building GraphQL schemas.&lt;/p&gt;&lt;h3&gt;Basic concepts of GraphQL&lt;/h3&gt;&lt;h4&gt;Queries&lt;/h4&gt;&lt;p&gt;In the example above we already saw an example of a GraphQL query. Let&amp;#x27;s look at its the basic parts:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1081px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b33f33e2243ca066570a2d326ed072f8/3e75c/graphql_query.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:41.86666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABOklEQVQoz3WRQU+DMBiG268thYJhKwO2oaKWTBPx4P+/e/ZHuC0m8zIvHlY+UwRky3Z4kvZt87RvS6SU75TSHwDYE0K+xwBAC2NsGF9g7xzORTjnn4QQdPi+j1prjOMYlVJttlwu8bmusaoqfDAGKVDs95/SuoQQH11gtdZNWZaNMaYpiqIhhLQApU0UhsP8DNY5WpcQYt0Jm/FpnudheL3AIE0wvr/Fyd0NRmWB/kyjnyYI0kNCh9s2nXB9JGSMudBdHfMsw6t0hvrlCdPXGoMsQbXIUaUJqkmMwGBc90jYVz5EUWRXq5UNgsDO53Mrfd+qRWanj8aCEK6W5YzZ6WTa7umqOg5DZc757uIjh+psbozBuq4xz/PTT9kRKeUbpfQLADYAsO1hjG2B0r+5G4/WaJ//s3EO5/oFt8qnjkHv3OIAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Basic parts of a GraphQL query.&quot; title=&quot;Basic parts of a GraphQL query.&quot; src=&quot;/static/b33f33e2243ca066570a2d326ed072f8/3e75c/graphql_query.png&quot; srcSet=&quot;/static/b33f33e2243ca066570a2d326ed072f8/12d18/graphql_query.png 375w,/static/b33f33e2243ca066570a2d326ed072f8/44507/graphql_query.png 750w,/static/b33f33e2243ca066570a2d326ed072f8/3e75c/graphql_query.png 1081w&quot; sizes=&quot;(max-width: 1081px) 100vw, 1081px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Basic parts of a GraphQL query.&lt;/div&gt;&lt;p&gt;As shown above, we can &lt;strong&gt;name&lt;/strong&gt; a query to be able to reference it.&lt;/p&gt;&lt;p&gt;Additionally, we can pass in &lt;strong&gt;arguments&lt;/strong&gt; into the query. In our example, the &lt;code&gt;id&lt;/code&gt; of an order is an argument. The exclamation point &lt;code&gt;!&lt;/code&gt; indicates, that the argument is required.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;field&lt;/strong&gt; is a unit of data, which ends up as a field in the JSON response data. For the query above, we could get the following JSON response:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
 order {
    name: &amp;quot;Dog soft frisbee&amp;quot;,
    status: &amp;quot;PAID&amp;quot;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;Mutations&lt;/h4&gt;&lt;p&gt;When working with APIs, we also need to be able to mutate, delete or create new data. As an equivalent to POST, PUT and DELETE requests in REST, so called &lt;code&gt;mutations&lt;/code&gt; are used in GraphQL.&lt;/p&gt;&lt;p&gt;Mutations consist of the same parts as a query. A mutation for creating a new order could look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;mutation CreateOrder($order: OrderInput!) {
    createOrder($order: OrderInput!) {
        id
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the mutation above, we create a new order by passing it in as an argument of type &lt;code&gt;OrderInput&lt;/code&gt;. The mutation will return the &lt;code&gt;id&lt;/code&gt; of the freshly created order in the response.&lt;/p&gt;&lt;h4&gt;GraphQL schema and explorer&lt;/h4&gt;&lt;p&gt;As a frontend developer, we need to know what kind of data we can ask for and what fields we can select. That&amp;#x27;s where the schema comes in.&lt;/p&gt;&lt;p&gt;Each GraphQL service defines a set of types which completely describe all possible data we can query or mutate as a client. When we send a query to that service, it will be executed against that schema to check if it&amp;#x27;s valid.&lt;/p&gt;&lt;p&gt;To explore that schema, GraphQL services often offer an in-browser exlorer like GraphiQL. With GraphiQL, we can write and test queries against the service. It offers a documentation and supports debugging as we type, giving hints and pointing out errors.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/0d3b6799a78e7eeaea4ea8bd0624c38e/9d81f/graphql_github_graphiql.jpg&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:50.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAd5BUR//xAAWEAEBAQAAAAAAAAAAAAAAAAAQATH/2gAIAQEAAQUCZh//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAXEAADAQAAAAAAAAAAAAAAAAAAARAR/9oACAEBAAE/IcighH//2gAMAwEAAgADAAAAEEDv/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxABAAIDAQEAAAAAAAAAAAAAAQARITFRkYH/2gAIAQEAAT8QKm32Ge+ylZqNSqsT13NPs//Z&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;GitHub&amp;#x27;s GraphQL explorer.&quot; title=&quot;GitHub&amp;#x27;s GraphQL explorer.&quot; src=&quot;/static/0d3b6799a78e7eeaea4ea8bd0624c38e/9d81f/graphql_github_graphiql.jpg&quot; srcSet=&quot;/static/0d3b6799a78e7eeaea4ea8bd0624c38e/f21ed/graphql_github_graphiql.jpg 375w,/static/0d3b6799a78e7eeaea4ea8bd0624c38e/88df8/graphql_github_graphiql.jpg 750w,/static/0d3b6799a78e7eeaea4ea8bd0624c38e/9d81f/graphql_github_graphiql.jpg 1500w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;GitHub&amp;#x27;s GraphQL explorer.&lt;/div&gt;&lt;h4&gt;Fragments&lt;/h4&gt;&lt;p&gt;The last GraphQL concept I like to introduce are &lt;strong&gt;fragments&lt;/strong&gt;. Fragments are reusable units that can be used when building more complicated queries. For example, we could extract the supplier behind an ordered product into a separate fragment to reuse it in different queries.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;fragment Supplier on Order {
    supplier {
        id
        name
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When fetching an order, we can now use the defined fragment as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  order($id: Id!) {
    name
    ...Supplier
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;GraphQL in iOS&lt;/h3&gt;&lt;p&gt;To be able to send requests to a GraphQL service from an iOS application, we could build the request on our own with the same tools we would use for sending REST requests.&lt;/p&gt;&lt;p&gt;For example, for sending a GraphQL query, you would send the query and its variables in the query parameter of a HTTP GET request.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;http://myapi/graphql?query={me{name...}}&amp;amp;variables={...}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or send it in the body of a POST request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;query&amp;quot;: &amp;quot;...&amp;quot;,
  &amp;quot;operationName&amp;quot;: &amp;quot;...&amp;quot;,
  &amp;quot;variables&amp;quot;: { &amp;quot;myVariable&amp;quot;: &amp;quot;someValue&amp;quot;, ... }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I prefer to use another way to communicate with a GraphQL service by using the library &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/apollographql/apollo-ios&quot;&gt;Apollo iOS Client&lt;/a&gt;. This networking library was specifically designed for working with GraphQL.&lt;/p&gt;&lt;p&gt;After we provide a schema file of the GraphQL service, the library will generate strongly typed Swift files out of our defined queries, which we can then use to send requests.&lt;/p&gt;&lt;p&gt;Here is an example of how sending a request looks like with Apollo:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;apollo.fetch(query: GetOrderQuery(id: id)) { result in
  switch result {
  case .success(let graphQLResult):
    // handle success.
  case .failure(let error):
    // handle error.
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To dive deeper, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/graphql-apollo-for-ios/&quot;&gt;getting started with Apollo iOS for GraphQL in Swift&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;I hope, I could give you a good introduction into the world of GraphQL. If you are new to GraphQL, I recommend playing around with &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.github.com/v4/explorer/&quot;&gt;Githubs GraphQL explorer&lt;/a&gt;, by reading through their docs and try out different types of queries. This will give you a good first impression on how GraphQL works in practice.&lt;/p&gt;&lt;p&gt;And of course, you can also dive deeper into the concepts of GraphQL by reading the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://graphql.org/learn/&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>graphql</category><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick tip on adding child view controllers programmatically in UIKit]]></title><description><![CDATA[A Swift extension for programmatically adding child view controllers.]]></description><link>https://tanaschita.com/uikit-adding-childview-controller</link><guid isPermaLink="false">https://tanaschita.com/uikit-adding-childview-controller</guid><pubDate>Sun, 21 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To better separate and organize our code, we have the possibility to nest view controllers inside each other.&lt;/p&gt;&lt;p&gt;The following code snippet provides an extension method for adding a child view controller programmatically. This extension method takes care of all the necessary steps, including setting constraints for the child view controller’s view.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension UIViewController {

    /// Utility method to add a `UIViewController` instance to a `UIView`.
    ///
    /// Calls all necessary methods for adding a child view controller and set the constraints
    /// between the views.
    ///
    /// - Parameters:
    ///   - viewController: `UIViewController` instance that will be added to `contentView`.
    ///   - contentView: `UIView` that will add the `childViewController` as its subview.
    func add(childViewController viewController: UIViewController, to contentView: UIView) {
        let matchParentConstraints = [
            viewController.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            viewController.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            viewController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
            viewController.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ]

        addChild(viewController)
        viewController.view.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(viewController.view)
        NSLayoutConstraint.activate(matchParentConstraints)
        viewController.didMove(toParent: self)
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>uikit</category><category>quick tip</category><category>ios</category><category>swift</category></item><item><title><![CDATA[My favorite Xcode keyboard shortcuts to be more productive]]></title><description><![CDATA[Increase your productivity when working with Xcode.]]></description><link>https://tanaschita.com/xcode-shortcuts</link><guid isPermaLink="false">https://tanaschita.com/xcode-shortcuts</guid><pubDate>Sun, 14 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Using keyboard shortcuts is key of being productive and efficient when working with a development environment like Xcode.
In this article, I&amp;#x27;ll show you my favorite Xcode shortcuts.&lt;/p&gt;&lt;p&gt;Before we start, here is a quick key reference:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;⌘&lt;/code&gt; - Command&lt;/li&gt;&lt;li&gt;&lt;code&gt;⇧&lt;/code&gt; - Shift&lt;/li&gt;&lt;li&gt;&lt;code&gt;⌥&lt;/code&gt; - Option/Alt&lt;/li&gt;&lt;li&gt;&lt;code&gt;⌃&lt;/code&gt; - Control&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;1. Product&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s start with some basic shortcuts which we can find in Xcode&amp;#x27;s &lt;code&gt;Product&lt;/code&gt; menu:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Run &lt;code&gt;⌘ R&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Test &lt;code&gt;⌘ U&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Clean Build Folder &lt;code&gt;⇧ ⌘ K&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Stop &lt;code&gt;⌘ .&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If you are working with Xcode for some time now, you most certainly use them already.&lt;/p&gt;&lt;h3&gt;2. Navigate&lt;/h3&gt;&lt;p&gt;Quickly &lt;strong&gt;navigating back and forth&lt;/strong&gt; when working on different files is a real time saver:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Go forward &lt;code&gt;⌃ ⌘ →&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Go back &lt;code&gt;⌃ ⌘ ←&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;3. Open quickly and jump to definition&lt;/h3&gt;&lt;p&gt;Another essential shortcut is one for &lt;strong&gt;opening a file quickly&lt;/strong&gt; with &lt;code&gt;⇧ ⌘ O&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;After doing so, the &lt;strong&gt;jump to definition&lt;/strong&gt; &lt;code&gt;⌃ ⌘ J&lt;/code&gt; shortcut comes in handy to jump to that file in the project navigator.&lt;/p&gt;&lt;h3&gt;4. Find&lt;/h3&gt;&lt;p&gt;Whether it&amp;#x27;s within the currently opened file or throughout the project/workspace, searching is another essential action:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Find &lt;code&gt;⌘ F&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Find and replace &lt;code&gt;⌥ ⌘ F&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Find in project / workspace &lt;code&gt;⇧ ⌘ F&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;5. View&lt;/h3&gt;&lt;p&gt;Being able to show and hide different Xcode areas quickly is really useful especially when working on smaller screens:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;⌘ 0&lt;/code&gt; to show and hide the project navigator&lt;/li&gt;&lt;li&gt;&lt;code&gt;⌘ ⇧ Y&lt;/code&gt; to show and hide the debug console&lt;/li&gt;&lt;li&gt;&lt;code&gt;⌘ ⌥ 0&lt;/code&gt; to show and hide the inspectors&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;6. Quickly open two files side by side&lt;/h3&gt;&lt;p&gt;I already mentioned the &lt;code&gt;Open quickly ⌘ ⇧ O&lt;/code&gt; command - which becomes even more powerful when using it with &lt;code&gt;⌥&lt;/code&gt;.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use &lt;code&gt;⌘ ⇧ O&lt;/code&gt; to open the dialog&lt;/li&gt;&lt;li&gt;Enter the file name you are searching for&lt;/li&gt;&lt;li&gt;Use &lt;code&gt;⌥&lt;/code&gt; in combination with enter to select the file. The new file will open in a separate editor allowing us to continue working on our current file while having access to the new opened file.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The &lt;code&gt;⌥&lt;/code&gt; tip also works when selecting a file in the project navigator.&lt;/p&gt;&lt;h3&gt;7. Quickly jump to a specific method in a file&lt;/h3&gt;&lt;p&gt;This shortcut comes in particularly handy for larger files with numerous methods, where scrolling quickly becomes tedious.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use &lt;code&gt;⌃ 6&lt;/code&gt; to open the documents structure&lt;/li&gt;&lt;li&gt;Start typing the methods name&lt;/li&gt;&lt;li&gt;Use enter to jump to the method.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;You can skip letters when typing, Xcode will do the work for you.&lt;/p&gt;&lt;h3&gt;8. Repeat the last test&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;⌃ ⌘ ⌥ G&lt;/code&gt; shortcut is especially useful when writing tests to repeat the last test we were running.&lt;/p&gt;&lt;h3&gt;9. Reindent code&lt;/h3&gt;&lt;p&gt;We can re-indent selected code in Xcode by pressing &lt;code&gt;⌃ I&lt;/code&gt; - which is useful when code is out of place, for example after refactoring.&lt;/p&gt;&lt;h3&gt;10. Disable / enable breakpoints&lt;/h3&gt;&lt;p&gt;This &lt;code&gt;⌘ Y&lt;/code&gt; shortcut comes in handy when debugging code to toggle between enabling and disabling breakpoint.&lt;/p&gt;</content:encoded><category>xcode</category><category>productivity</category><category>ios</category></item><item><title><![CDATA[Introduction to Combine for iOS developers with RxSwift experience]]></title><description><![CDATA[How to start using Combine for reactive programming]]></description><link>https://tanaschita.com/combine-for-rxswift-devs</link><guid isPermaLink="false">https://tanaschita.com/combine-for-rxswift-devs</guid><pubDate>Sun, 07 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are already exprerienced with RxSwift, switching to Combine will mostly be a matter of learning new namings for similar concepts.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Publisher &amp;amp; Subscriber&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;Publisher&lt;/strong&gt; is what in RxSwift is an &lt;strong&gt;Observable&lt;/strong&gt;. A publisher defines how values are produced and allows registration of a subscriber.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;Subscriber&lt;/strong&gt; is what in RxSwift is an &lt;strong&gt;Observer&lt;/strong&gt;. A subscriber receives values from the publisher after subscribing.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s remind how to create and subscribe to an Observable in RxSwift first:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let observable = Observable.from([1, 2, 3, 4])
observable.subscribe(onNext: { value in
    print(value)
}, onCompleted: {
    print(&amp;quot;completed&amp;quot;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here how it looks to create and register to a Publisher in Combine:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let publisher = Publishers.Sequence&amp;lt;[Int], Error&amp;gt;(sequence: [1, 2, 3, 4])
publisher.sink(receiveCompletion: { _ in
    print(&amp;quot;completed&amp;quot;)
}) { value in
    print(value)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both examples produce the same output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;1
2
3
4
completed
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;SubjectType&lt;/h3&gt;&lt;p&gt;A &lt;strong&gt;SubjectType&lt;/strong&gt; is what in RxSwift is a &lt;strong&gt;Subject&lt;/strong&gt;, so the name is not that different in this case. Only the subtypes of a subject were named differently.&lt;/p&gt;&lt;p&gt;There are two kinds of a subject type in Combine: PassthroughSubject and CurrentValueSubject.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;PassthroughSubject&lt;/strong&gt; is what in RxSwift is a &lt;strong&gt;PublishSubject&lt;/strong&gt;. If you subscribe to it you will get all events that happen after you subscribed.&lt;/p&gt;&lt;p&gt;A &lt;strong&gt;CurrentValueSubject&lt;/strong&gt; is what in RxSwift is a &lt;strong&gt;BehaviourSubject&lt;/strong&gt;. It behaves like a PassthroughSubject but it also will send it&amp;#x27;s most recent element to new subscribers.&lt;/p&gt;&lt;p&gt;In RxSwift, creating a subject and sending values to the observer might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let subject = PublishSubject&amp;lt;String&amp;gt;()
subject.asObservable().subscribe(onNext: { value in
    print(value)
})

subject.onNext(&amp;quot;Yes&amp;quot;)
subject.onNext(&amp;quot;Ve&amp;quot;)
subject.onNext(&amp;quot;gan&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The equivalent in Combine:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let subject = PassthroughSubject&amp;lt;String, Never&amp;gt;()
subject.eraseToAnyPublisher().sink { value in
    print(value)
}

subject.send(&amp;quot;Yes&amp;quot;)
subject.send(&amp;quot;Ve&amp;quot;)
subject.send(&amp;quot;gan&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both examples produce the same output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Yes
Ve
gan
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you like going deeper, you can find a good comparison of available RxSwift und Combine components &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/freak4pc/rxswift-to-combine-cheatsheet/blob/master/Data/core_components.csv&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Operators&lt;/h3&gt;&lt;p&gt;Many operators like &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;flatMap&lt;/code&gt;, &lt;code&gt;reduce&lt;/code&gt; etc. that are available in RxSwift are also available in Combine. Using Combine operators is just like using RxSwift operators.&lt;/p&gt;&lt;p&gt;So it should feel really familiar to see Combine operators in action:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Publishers.Sequence(sequence: [1, 2, 2, 3, 3, 4, 7])
    .map { $0 * 2 }
    .filter { $0.isMultiple(of: 2) }
    .dropFirst(3)
    .removeDuplicates()
    .sink { value in
        print(value)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This code produces the following output:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;6
8
14
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A full comparison list of RxSwift und Combine operators is available &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/freak4pc/rxswift-to-combine-cheatsheet/blob/master/Data/operators.csv&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Error Handling&lt;/h2&gt;&lt;p&gt;A notable difference between RxSwift’s &lt;code&gt;Observable&lt;/code&gt; and Combine’s &lt;code&gt;Publisher&lt;/code&gt; is the error handling. In RxSwift, we work with the general &lt;code&gt;Error&lt;/code&gt; type whereas in Combine we can use any kind of custom error:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let publisher = Publishers.Sequence&amp;lt;[Int], SomeCustomError&amp;gt;(sequence: [1, 2])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If a subject can never fail, Combine provides the &lt;code&gt;Never&lt;/code&gt; type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let subject = PassthroughSubject&amp;lt;String, Never&amp;gt;()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Memory Management&lt;/h2&gt;&lt;p&gt;To deal with ARC and memory management, RxSwift provides a &lt;code&gt;DisposeBag&lt;/code&gt;. In Combine, we can use a collection of &lt;code&gt;AnyCancellable&lt;/code&gt;s.&lt;/p&gt;&lt;p&gt;Here is an example of how a &lt;code&gt;DisposeBag&lt;/code&gt; is typically used in RxSwift:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let disposeBag = DisposeBag()

let subject = PublishSubject&amp;lt;String&amp;gt;()
subject.asObservable().subscribe(onNext: { value in
    print(value)
}).disposed(by: disposeBag)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is the equivalent example in Combine:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;var cancellables = Set&amp;lt;AnyCancellable&amp;gt;()

let subject = PassthroughSubject&amp;lt;String, Never&amp;gt;()
subject.eraseToAnyPublisher().sink { value in
    print(value)
}.store(in: &amp;amp;cancellables)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Performance&lt;/h2&gt;&lt;p&gt;I didn&amp;#x27;t compare the performance of RxSwift and Combine myself but there are some great articles about it. They all agree that Combine is clearly faster than RxSwift.&lt;/p&gt;&lt;p&gt;If you are interested in more specifics, you can find some performance tests and its results &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://github.com/quickbirdstudios/CombineRxSwiftPerformance&quot;&gt;here&lt;/a&gt;. The tests compare the performance of the most commonly used components and operators in RxSwift and Combine.&lt;/p&gt;</content:encoded><category>combine</category><category>reactive programming</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick tip on how to learn Xcode keyboard shortcuts efficiently]]></title><description><![CDATA[A workflow to memorize Xcode keyboard shortcuts.]]></description><link>https://tanaschita.com/xcode-quicktip-learning-shortcuts</link><guid isPermaLink="false">https://tanaschita.com/xcode-quicktip-learning-shortcuts</guid><pubDate>Sun, 07 Apr 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Knowing Xcode keyboard shortcuts is key to be more productive while developing iOS applications. But memorizing a lot of shortcuts can prove tedious, especially when starting out.&lt;/p&gt;&lt;p&gt;The following quick tip allowed me to integrate an efficient learning process into my development workflow.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The key is to be able to lookup a shortcut real quick, so we can learn them on the fly while developing.&lt;/p&gt;&lt;p&gt;An efficient way to lookup a shortcut quickly is by opening Xcodes help menu and typing in the action. Xcode will make suggestions so we can directly see the shortcut, use the escape key to close the help menu and then use the looked up shortcut to perform the action.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1352px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/220a31596e39225775414751286e8ace/3c65a/xcode-quick-tip-shortcuts.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:26.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABTUlEQVQY0zXPSy8DYRTG8fly08TCLbGwkFhJEIQPgAaNa03pZeoynek78847vRB3YoMQ4hKK1q3Y+CB/6YjFL89ZnOQ8R6vXa1xdnnJzfU7l4Yb316cwX2oVqs/3PD/ecntxxGf1ip/vGvWPKp/1P43539tLhe+vV7S+3l6yponrCqTn4fuSIFAEQYAvPbJrFsPLB/SnzlgWO5QKPkoplPJDjf2GfN4hGo2iRXQdw1jENDPYOQvHzuE4No7jkLPWmYsn6Ipt0zK+y1SmSFkJigXFZrnARimgXAooBpK93S2GBgfQIhGddDpFNmsipRe2alyUUiI9QWzeoC++T/fsEU1jh+ijxzRPndI+c0Hb9J/myRM64nfonYNoLa2tJFNJMmYGIUT4uuu5CFeE4obBhLFOLLHKXNJiZmmFhbSFYdqhxEqedE5hq016Rkb5BYw7IIesi7BFAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Workflow example&quot; title=&quot;Workflow example&quot; src=&quot;/static/220a31596e39225775414751286e8ace/3c65a/xcode-quick-tip-shortcuts.png&quot; srcSet=&quot;/static/220a31596e39225775414751286e8ace/12d18/xcode-quick-tip-shortcuts.png 375w,/static/220a31596e39225775414751286e8ace/44507/xcode-quick-tip-shortcuts.png 750w,/static/220a31596e39225775414751286e8ace/3c65a/xcode-quick-tip-shortcuts.png 1352w&quot; sizes=&quot;(max-width: 1352px) 100vw, 1352px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;For me, this technique payed out real soon, I memorized the shortcuts automatically when using them on a daily basis.&lt;/p&gt;&lt;p&gt;Since there is no keyboard shortcut for opening Xcodes help menu, we can create our own custom shortcut by going to &lt;code&gt;System Settings&lt;/code&gt; &amp;gt; &lt;code&gt;Keyboard&lt;/code&gt; &amp;gt; &lt;code&gt;Shortcuts&lt;/code&gt; &amp;gt; &lt;code&gt;App Shortcuts&lt;/code&gt; &amp;gt; &lt;code&gt;Show Help menu&lt;/code&gt;.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1326px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/c211bca61af32752b2cb695a5d618f27/4c147/xcode-show-help-menu-shortcut.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:51.2%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABT0lEQVQoz6WQ3UrDQBBG86a+gbfila8gvoEIXggqeleQiCAUa5psmjTdxpi0VBJUUvOfJqn5ZEcqKL1pXTiwzM4evhlJtaZQmQHGNJimCcuyMJlMUNc1wjBEHMcoigJ5niNJEiJNU6qv7gLRv1wuIZ3KHHddFaahg3MOx3FIuKgqFHmONEkwn88RRRGqqkLTNPT5L6JOwlv5Br3eA3Rdx2g0+hHWixKv0QJ8+o4xt8D5GEEQUNJ1+L5PSSV9IMbVSWjbNlzXJWFZlviIU0RJjjzLqFmMuE4mViLexB9pOBySTNO+d+h5HiHib3NIKGSKooAxRnsUCVfCtm03QtI0hr6qgekDjO0n2PYTPO8fwseeAoMp4KaKYOYSL1P3l3CjkY/kN5zIzziXDVzfO+j0fXTNGdrPLYUHVz52z2LsX2bYu0ixc5zhsBMCbYN2C+EXwxDz3s4M4Z8AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Creating shortcut for opening Xcode&amp;#x27;s help menu&quot; title=&quot;Creating shortcut for opening Xcode&amp;#x27;s help menu&quot; src=&quot;/static/c211bca61af32752b2cb695a5d618f27/4c147/xcode-show-help-menu-shortcut.png&quot; srcSet=&quot;/static/c211bca61af32752b2cb695a5d618f27/12d18/xcode-show-help-menu-shortcut.png 375w,/static/c211bca61af32752b2cb695a5d618f27/44507/xcode-show-help-menu-shortcut.png 750w,/static/c211bca61af32752b2cb695a5d618f27/4c147/xcode-show-help-menu-shortcut.png 1326w&quot; sizes=&quot;(max-width: 1326px) 100vw, 1326px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;</content:encoded><category>xcode</category><category>quick tip</category><category>productivity</category></item><item><title><![CDATA[How to avoid using AnyView in SwiftUI]]></title><description><![CDATA[By using alternatives such as the @ViewBuilder attribute, Group type or generics.]]></description><link>https://tanaschita.com/swiftui-how-to-avoid-using-anyview</link><guid isPermaLink="false">https://tanaschita.com/swiftui-how-to-avoid-using-anyview</guid><pubDate>Sun, 31 Mar 2024 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI provides the &lt;em&gt;type-erased view&lt;/em&gt; &lt;code&gt;AnyView&lt;/code&gt; that can be used as a wrapper for any other SwiftUI view, for example to be able to return multiple view types from a function.&lt;/p&gt;&lt;p&gt;While using &lt;code&gt;AnyView&lt;/code&gt; might be a valid choice in some cases, we should use alternatives where possible. The reason is that SwiftUI works with a so called &lt;em&gt;structural identity&lt;/em&gt; mechanism, which uses the view&amp;#x27;s type to identify it and to determine when they should be updated. Since &lt;code&gt;AnyView&lt;/code&gt; erases the type of the view, it reduces SwiftUI’s ability to efficiently update the view.&lt;/p&gt;&lt;p&gt;There are different possibilities which can help us avoiding &lt;code&gt;AnyView&lt;/code&gt;:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Using &lt;code&gt;@ViewBuilder&lt;/code&gt; attribute instead of &lt;code&gt;AnyView&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Using &lt;code&gt;Group&lt;/code&gt; instead of &lt;code&gt;AnyView&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Using Generics instead of &lt;code&gt;AnyView&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s jump in and look at each possibility in more detail.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Using &lt;code&gt;@ViewBuilder&lt;/code&gt; attribute instead of &lt;code&gt;AnyView&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s look at an example where we might be tempted to use &lt;code&gt;AnyView&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;private var nameView: some View {
    if isEditable {
        return TextField(&amp;quot;Your name&amp;quot;, text: $name)
    } else {
        return Text(name)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When defining view properties or functions that return a view, we often use the &lt;code&gt;some View&lt;/code&gt; opaque return type so we don&amp;#x27;t need to explicitly define an exact return type.&lt;/p&gt;&lt;p&gt;But the above code won&amp;#x27;t compile with the error message &lt;code&gt;Function declares an opaque return type, but the return statements in its body do not have matching underlying types.&lt;/code&gt;&lt;/p&gt;&lt;p&gt;To resolve the error and return the same type for both views, we might be tempted to wrap them in &lt;code&gt;AnyView&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;private var nameView: some View {
    if isEditable {
        return AnyView(TextField(&amp;quot;Your name&amp;quot;, text: $name))
    } else {
        return AnyView(Text(name))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But there is a more elegant and more performant solution to solve the compiler error - the &lt;code&gt;@ViewBuilder&lt;/code&gt; attribute.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;@ViewBuilder&lt;/code&gt; attribute allows us to compose multiple views into a single return type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@ViewBuilder
private var nameView: some View {
    if isEditable {
        TextField(&amp;quot;Your name&amp;quot;, text: $name)
    } else {
        Text(name)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All we have to do is to add the attribute to our property or function and remove the return statements.&lt;/p&gt;&lt;p&gt;That&amp;#x27;s the same mechanism the &lt;code&gt;body&lt;/code&gt; of a SwiftUI view uses. The only difference is that we explicitly have to add the &lt;code&gt;ViewBuilder&lt;/code&gt; attribute on our own properties and functions.&lt;/p&gt;&lt;h3&gt;Using Group instead of AnyView&lt;/h3&gt;&lt;p&gt;By using the &lt;code&gt;Group&lt;/code&gt; type, we can collect multiple views into a single one without affecting the layout of those views.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;private var nameView: some View {
    Group {
        if isEditable {
            TextField(&amp;quot;Your name&amp;quot;, text: $name)
        } else {
            Text(name)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since &lt;code&gt;Group&lt;/code&gt; uses ViewBuilder, we can group different kinds of views with conditional statements.&lt;/p&gt;&lt;h3&gt;Using Generics instead of AnyView&lt;/h3&gt;&lt;p&gt;Another common situation where we might be tempted to use &lt;code&gt;AnyView&lt;/code&gt; is when we need to store a view without knowing its type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct FlyoutView: View {

    let headerView: AnyView

    var body: some View {
        VStack {
            headerView
            Spacer()
            // other views
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we want the &lt;code&gt;headerView&lt;/code&gt; to be of a flexible type. Since we can&amp;#x27;t use &lt;code&gt;some View&lt;/code&gt; for stored properties, &lt;code&gt;AnyView&lt;/code&gt; seems like a good choice.&lt;/p&gt;&lt;p&gt;A more elegant and performant solution would be using generics.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct FlyoutView&amp;lt;HeaderView&amp;gt; : View where HeaderView : View {

    let headerView: HeaderView

    var body: some View {
        VStack {
            headerView
            Spacer()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When creating a &lt;code&gt;FlyoutView&lt;/code&gt;, we don&amp;#x27;t need to wrap the header view in &lt;code&gt;AnyView&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;FlyoutView(headerView: Text(&amp;quot;Header view&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same generics approach is used by many views provided by SwiftUI.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct VStack&amp;lt;Content&amp;gt; : View where Content : View {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the content view of a &lt;code&gt;VStack&lt;/code&gt; is of a generic type that conforms to &lt;code&gt;View&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Using &lt;code&gt;AnyView&lt;/code&gt; can be avoided in most situations. The shown alternatives not only add more performance to our SwiftUI views, they also result in a more elegant and readable code.&lt;/p&gt;</content:encoded><category>swiftui</category><category>performance</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to use Swift's logging API]]></title><description><![CDATA[Learn how to log events and errors in your iOS app with Swift's logging API.]]></description><link>https://tanaschita.com/swift-logging-api</link><guid isPermaLink="false">https://tanaschita.com/swift-logging-api</guid><pubDate>Sun, 24 Mar 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Logging is an essential aspect in software development, enabling developers to track and understand the behavior of their applications.&lt;/p&gt;&lt;p&gt;Swift&amp;#x27;s logging API is part of the &lt;code&gt;os&lt;/code&gt; framework providing different logging levels, categories, persistence, formatting and privacy features.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to use it.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and look at the following example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;import os

let logger = Logger()
logger.log(&amp;quot;Starting video with title \(title, privacy: .public)&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can log any type confirming to &lt;code&gt;CustomStringConvertable&lt;/code&gt;. The log outputs appear in Xcode&amp;#x27;s console while running the app. With a connected device, we can use the &lt;code&gt;Console.app&lt;/code&gt; from the &lt;code&gt;Utilities&lt;/code&gt; folder.&lt;/p&gt;&lt;p&gt;Alternatively, we could initialize the logger as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let logger = Logger(subsystem: &amp;quot;someSubsytem&amp;quot;, category: &amp;quot;someCategory&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;the &lt;code&gt;subsystem&lt;/code&gt; parameter can for example be the bundle identifier of your app.&lt;/li&gt;&lt;li&gt;the &lt;code&gt;category&lt;/code&gt; parameter can be used to structure logs from different parts of the app.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Logging Levels&lt;/h3&gt;&lt;p&gt;The API provides five log levels:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;Debug&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Info&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Notice&lt;/code&gt; (default)&lt;/li&gt;&lt;li&gt;&lt;code&gt;Error&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;Fault&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The &lt;code&gt;Logger&lt;/code&gt; instance provides corresponding methods such as &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt; etc. For example, to log an API error:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;logger.error(&amp;quot;Missing field \(field) in response from \(endpoint).&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Persistence&lt;/h3&gt;&lt;p&gt;Logs are archived to retrieve them later from the device. To browse through persisted messages from your device, connect the device and open &lt;code&gt;Console.app&lt;/code&gt; that is preinstalled in the &lt;code&gt;Utilities&lt;/code&gt; folder.&lt;/p&gt;&lt;p&gt;Only logs that are persisted can be retrieved after execution. Whether or not a log message is persisted depends on the log level. The &lt;code&gt;Notice&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt; and &lt;code&gt;Fault&lt;/code&gt; messages are persisted.&lt;/p&gt;&lt;h3&gt;Performance&lt;/h3&gt;&lt;p&gt;According to Apple, the logging system is very performant, so you can use it widely in your app without slowing it down. Unlike with &lt;code&gt;print&lt;/code&gt;, log messages are not fully converted into a string representation. Instead, the compiler and the logging library work together to produce an optimized representation of the log message.&lt;/p&gt;&lt;h3&gt;Privacy&lt;/h3&gt;&lt;p&gt;Nonnumeric values in your logs are configured as private by default, so they are hidden in the message. This makes sure, that after shipping the app, the logs don&amp;#x27;t show any personal information. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;logger.log(&amp;quot;Logging in user with email \(emailAddress)&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The statement above will produce the following log message:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Logging in user with email &amp;lt;private&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the information you are logging should not be hidden, you have to explicitly mark it as public:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;logger.log(&amp;quot;Opening video \(videoId, privacy: .public)&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;One thing I&amp;#x27;m personally missing when comparing the API with third party libraries, is the ability to retrieve all messages from the &lt;code&gt;Logger&lt;/code&gt; instance, for example to be able to show the logs directly within the application.&lt;/p&gt;</content:encoded><category>swift</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[How to support dark mode in SwiftUI programmatically]]></title><description><![CDATA[Learn how to improve type safety when working with semantic colors.]]></description><link>https://tanaschita.com/ios-supporting-dark-mode-programmatically</link><guid isPermaLink="false">https://tanaschita.com/ios-supporting-dark-mode-programmatically</guid><pubDate>Sun, 17 Mar 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To add support for dark mode in an iOS application, Apple provides so called semantic colors which means they can have multiple values.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to define and use those semantic colors in code without using the asset catalog.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;At first, let&amp;#x27;s take a short look at how to define semantic colors via an asset catalog. Xcode provides a convenient way to do so:&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1292px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/de8d322cbe4730c4fb2dad21b0e9bd70/51bf4/20240311-dark-mode-asset-catalog.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:39.2%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABrElEQVQoz12Ru47TQBSG8zKbkDuxs3ESezzHHttxnCjhkqzSoKBFothdiRKBREGDKKCipEJUCFGhbXkSHoF3+NBMonApjjTSzPn0/fPXdrsdxhi01iQiiAixpChtiLVBS4KIpixLfN+j02kTxzGSZigV4XkDBoO7BMGI4dCnZmEqUizvrXn66gWPnt2w3e/58OMT775/JC0qYqVI09Qt97ptgkmIytcUeUa316XVatJs3nFTk0SIwpDV5iHX79/w+OVz9leX3P665evPbxRliVKKJEnwPO+0qKKpMw2CgMlk7Aytfc0+tAvL1YrLmyv2T/ZED665ePuF+68/E+QXSBwiSXoCtjttB7FfYa1tVN/36XY71OyfadHOQLSmzDPm6x0j2eCrNZEUiI4RORg2GnUXMc8ziiJ3wHr9jEb9jFazcQRqfRpJEnKTYJKIVCIH0/pQli2l3++5EoxJWS4XRFHIOBgxnob0vNEfoBwbtufYwp35cY73Ntp4HHB+PmSxqNhuN1TVHB0rlBj6I/Uv8G/w/+aHUgYu7nQ6oaoq5vOS2awgz1LMbMFgavgNIcEb1ZPZrFIAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Defining semantic colors via asset catalog.&quot; title=&quot;Defining semantic colors via asset catalog.&quot; src=&quot;/static/de8d322cbe4730c4fb2dad21b0e9bd70/51bf4/20240311-dark-mode-asset-catalog.png&quot; srcSet=&quot;/static/de8d322cbe4730c4fb2dad21b0e9bd70/12d18/20240311-dark-mode-asset-catalog.png 375w,/static/de8d322cbe4730c4fb2dad21b0e9bd70/44507/20240311-dark-mode-asset-catalog.png 750w,/static/de8d322cbe4730c4fb2dad21b0e9bd70/51bf4/20240311-dark-mode-asset-catalog.png 1292w&quot; sizes=&quot;(max-width: 1292px) 100vw, 1292px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Defining semantic colors via asset catalog.&lt;/div&gt;&lt;/undefined&gt;&lt;p&gt;To use that color in code, we call:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Color(&amp;quot;primary&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working with raw strings as shown in the example above, we loose type safety. To improve that, we could either&lt;/p&gt;&lt;ul&gt;&lt;li&gt;combine asset catalog with encapsulation&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;or&lt;/p&gt;&lt;ul&gt;&lt;li&gt;define these colors programmatically&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The first option could look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension Color {
    static let primary = Color(&amp;quot;primary&amp;quot;)
    static let secondary = Color(&amp;quot;secondary&amp;quot;)
}

Text(&amp;quot;Some text&amp;quot;)
     .foregroundColor(.primary)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another way is to not to use asset catalogs entirely but to define colors programmatically instead.&lt;/p&gt;&lt;h3&gt;Defining colors programmatically&lt;/h3&gt;&lt;p&gt;For the programmatic approach, we can use the &lt;code&gt;userInterfaceStyle&lt;/code&gt; property of &lt;code&gt;UITraitCollection&lt;/code&gt; to find out which color to use:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension UIColor {
    static func dynamicColor(light: UIColor, dark: UIColor) -&amp;gt; UIColor {
        return UIColor { $0.userInterfaceStyle == .dark ? dark : light }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the extension in place, we can use it as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct Colors {
    var primary: Color {
        return color(light: UIColor(red: 0.7, green: 1, blue: 0.7, alpha: 1),
                     dark: UIColor(red: 0.5, green: 1, blue: 0.5, alpha: 1))

    private func color(light: UIColor, dark: UIColor) -&amp;gt; Color {
        return Color(UIColor.dynamicColor(light: light, dark: dark))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively, we can use the same approach in a color extension.&lt;/p&gt;&lt;h3&gt;Only supporting light mode&lt;/h3&gt;&lt;p&gt;A quick tip at the end: as long as you are not supporting dark mode, set &lt;code&gt;Info.plist&lt;/code&gt; key &lt;code&gt;UIUserInterfaceStyle&lt;/code&gt; to &lt;code&gt;Light&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This way, we can avoid unwanted appearance effects when users use your app with dark mode turned on.&lt;/p&gt;</content:encoded><category>ux</category><category>swiftui</category><category>swift</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Safe array subscription in Swift]]></title><description><![CDATA[An extension to access array elements with conditional binding]]></description><link>https://tanaschita.com/swift-basics-safe-array-subscription</link><guid isPermaLink="false">https://tanaschita.com/swift-basics-safe-array-subscription</guid><pubDate>Sun, 17 Mar 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The possibility to use optionals in Swift allows us to write safe and expressive code. However, accessing elements from an array is not safe by default.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at an example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let shoppingList = [&amp;quot;apples&amp;quot;, &amp;quot;raspberries&amp;quot;]
let item = shoppingList[2]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Accessing an element this way will crash the app if the element isn&amp;#x27;t there.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;By adding the following extension to our code base, we have the possibility to access it in a safe way.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension Collection {

    /// Returns the element at the specified index if it exists, otherwise nil.
    subscript (safe index: Index) -&amp;gt; Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We know can access the item with conditional binding:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;if let item = shoppingList[safe: 2] {
    // use item
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swift</category><category>quick tip</category><category>basics</category></item><item><title><![CDATA[How to use TipKit to create tool tips in SwiftUI]]></title><description><![CDATA[Learn how to show feature hints as a popover to the user by using TipKit.]]></description><link>https://tanaschita.com/20240304-tipkit-feature-hints</link><guid isPermaLink="false">https://tanaschita.com/20240304-tipkit-feature-hints</guid><pubDate>Sun, 03 Mar 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC23, Apple introduced TipKit which allows us to show feature hints to the user with a few lines of code.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and see how it works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The following image shows an example of a TipKit popover hint that is shown over a toolbar button when the user opens the view for the first time.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1372px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1a88afdcd531cfdb2e39a55733a17197/14c3e/20240301-tipkit-example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:40%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABh0lEQVQoz32R3U/TUBjGO2a7teuwHcmU0Y/1Y/3Y7GnZCk4MZFhAwMQAalzU6IXX/v+XP9MSlxgIF0+ekyc5v/O875GSNCedFtQehAm2PcY0B+i6jmEYmKaJYZqN13mtfn+7keN4xIkgEwfkxWtEfohUg/4pTrIGJMvPsGyLrXYLSZIeaLAzwPM9VFVlNttH5PewB8AgiDeXVE27P7fbdDUVrachK3KTKR0Fva/T2mrhB9Gm3QZYj5xOcxzXw/M8Xuzt0NluMdrTmbpDZomPZY8wBs9RugpyR0btqXS6Cu44QDwGTFKB50Wsf9xRfY24eGOzrgS/P7/n2+0V1eqEslwwmUywLKt5WOupDfDRhlGSEUeveHdeEB8o3Bwl3L0V/PrykZ/rT3y4ukSIjDRNiaIIx3VQtSeAcSrwvRBxUeGeFsxLwfJwn9PVMcujJfOyZL5YUBQFWZYRhuHTDWt3bRf1+JrR9z8Eq3PS6pJJdY1/coabpLhjt2m2u/uS4XDY7HA8Dv/75b/vq+Lc9mKKjAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;TipKit popover example.&quot; title=&quot;TipKit popover example.&quot; src=&quot;/static/1a88afdcd531cfdb2e39a55733a17197/14c3e/20240301-tipkit-example.png&quot; srcSet=&quot;/static/1a88afdcd531cfdb2e39a55733a17197/12d18/20240301-tipkit-example.png 375w,/static/1a88afdcd531cfdb2e39a55733a17197/44507/20240301-tipkit-example.png 750w,/static/1a88afdcd531cfdb2e39a55733a17197/14c3e/20240301-tipkit-example.png 1372w&quot; sizes=&quot;(max-width: 1372px) 100vw, 1372px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Example of a TipKit popover&lt;/div&gt;&lt;p&gt;The tip shown above can be implemented by following these steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Defining a tip which conforms to the &lt;code&gt;Tip&lt;/code&gt; protocol&lt;/li&gt;&lt;li&gt;Initializing and configuring tips on app start&lt;/li&gt;&lt;li&gt;Displaying the tip on top of the desired view using popover tips&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;h3&gt;1. Defining a tip which conforms to the &lt;code&gt;Tip&lt;/code&gt; protocol&lt;/h3&gt;&lt;p&gt;The first step is to define our custom tip which conforms to &lt;code&gt;Tip&lt;/code&gt; which is a protocol defined by &lt;code&gt;TipKit&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import TipKit

struct EditShoppingListRecipesTip: Tip {
    // 1.
    var title: Text {
        Text(LocalizedStringKey(&amp;quot;common.shoppingList.edit.tip.title&amp;quot;))
    }

    var message: Text? {
        Text(LocalizedStringKey(&amp;quot;common.shoppingList.edit.tip.message&amp;quot;))
    }

    var image: Image? {
        Image(systemName: &amp;quot;square.and.pencil&amp;quot;)
    }

    // 2.
    var options: [Option] {
        [MaxDisplayCount(1), IgnoresDisplayFrequency(true)]
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;The &lt;code&gt;Tip&lt;/code&gt; protocol provides different properties which we can use to configure the tip&amp;#x27;s UI components like &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Additionally, we can specify the &lt;code&gt;options&lt;/code&gt; property, for example to configure the maximum number of times a tip displays or to control whether a tip obeys the preconfigured display frequency interval.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Furthermore, we could specify &lt;code&gt;rules&lt;/code&gt; for our tip to add even more control when to show it to the user. To learn more about rules, check out this &lt;a target=&quot;_blank&quot; href=&quot;/&quot;&gt;article (coming soon)&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;2. Initializing and configuring tips on app start&lt;/h3&gt;&lt;p&gt;The next step is to initialize tips on app start. All we need to do here is to call &lt;code&gt;Tips.configure()&lt;/code&gt; once per app session, for example in the &lt;code&gt;init()&lt;/code&gt; method of the app.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct RecipesApp: App {

    ...

    var body: some Scene {
        WindowGroup {
            AppRootView()
                ...
        }
    }

    init() {
        try? Tips.configure()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If needed, we could pass in a configuration, for example to set the display frequency:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Tips.configure([.displayFrequency(.daily)])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When set, the system will not show more than one tip per day.&lt;/p&gt;&lt;h3&gt;3. Displaying the tip on top of the desired view using popover tips&lt;/h3&gt;&lt;p&gt;The last step is to call the tip by using SwiftUI&amp;#x27;s &lt;code&gt;popoverTip&lt;/code&gt; modifier.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;private var editingModeTip = EditShoppingListRecipesTip()

var body: some View {
    ...
    Image(systemName: &amp;quot;square.and.pencil&amp;quot;)
        .popoverTip(editingModeTip)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that in place, the system will show the tip to the user once the editing button becomes visible.&lt;/p&gt;&lt;p&gt;Instead of a popover, &lt;code&gt;TipKit&lt;/code&gt; also provides the possibility to show the tip inside any SwiftUI view by using &lt;code&gt;TipView&lt;/code&gt;, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack {
    TipView(tip)
    ContentView()
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>ux</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to delay an animation in SwiftUI]]></title><description><![CDATA[Learn how to wait for a certain amount of time before starting a SwiftUI animation.]]></description><link>https://tanaschita.com/20240226-delay-swiftui-animation</link><guid isPermaLink="false">https://tanaschita.com/20240226-delay-swiftui-animation</guid><pubDate>Sun, 25 Feb 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with animations in SwiftUI, we sometimes need to delay an animation, for example when showing a UI element to users and then hiding it after a certain amount of time.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump right in and see how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s look at the following example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@State private var offset = -60.0 // 1.

var body: some View {
    ErrorView()
        .offset(y: offset)
        .onAppear {
            // 2.
            withAnimation(.bouncy) {
                offset = 60.0
            } completion: {
                // 3.
                withAnimation(.bouncy) {
                    offset = -60.0
                }
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What we do in the code above is:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Setting the initial offset of the view out of screen bounds so it&amp;#x27;s not visible at first.&lt;/li&gt;&lt;li&gt;As soon as the view appears, we animate it to a different offset so it becomes visible on the screen.&lt;/li&gt;&lt;li&gt;As soon as the animation is completed, we animate the view back to a non-visible offset.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;What is missing in the example is a delay, so the view stays on the screen for a few seconds before bouncing back.&lt;/p&gt;&lt;p&gt;Since delays are built-in into SwiftUI animations, we can achieve this with one simple call. All we need to do is to add a delay to our second animation:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;withAnimation(.bouncy.delay(3))
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>swiftui</category><category>animations</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to define one-to-many relationships in SwiftData]]></title><description><![CDATA[Learn to use SwiftData's @Relationship macro.]]></description><link>https://tanaschita.com/swiftdata-one-to-many-relationships</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-one-to-many-relationships</guid><pubDate>Sun, 18 Feb 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, we will look at how to define one-to-many relationships in SwiftData. If you are new to SwiftData, check out this &lt;a target=&quot;_blank&quot; href=&quot;/swiftdata-quick-developer-guide/&quot;&gt;developer guide on SwiftData&lt;/a&gt; first.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Relationships in SwiftData can only be defined between reference types, i.e. classes. Let&amp;#x27;s look at an example of a one-to-many relationship:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Model
class User {
    @Relationship(inverse: \Note.user) var notes: [Note]?

    init(notes: [Note]? = nil) {
        self.notes = notes
    }
}

@Model
class Note {
    var user: User
    var title: String
    var text: String

    init(user: User, title: String, text: String) {
        self.user = user
        self.title = title
        self.text = text
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we use the &lt;code&gt;@Relationship&lt;/code&gt; macro to define a one-to-many relationship between a user and their notes. With that in place, if we now create a new &lt;code&gt;Note&lt;/code&gt; for example, this note will automatically be added to the user&amp;#x27;s list of notes.&lt;/p&gt;&lt;p&gt;An interesting fact to know here is that when both relationship ends are declared as optional, SwiftData will automatically infer an inverse relationship even without the &lt;code&gt;@Relationship&lt;/code&gt; macro.&lt;/p&gt;&lt;p&gt;Furthermore, we can use the &lt;code&gt;@Relationship&lt;/code&gt; macro to specify delete rules. By default, SwiftData uses the &lt;code&gt;.nullify&lt;/code&gt; delete rule which only nullifies the related model&amp;#x27;s reference to the deleted model. That means that if in our example above a &lt;code&gt;User&lt;/code&gt; gets deleted, then their notes will stay intact in our data store. In some cases that&amp;#x27;s the behaviour we want, but in our example, we&amp;#x27;d like to delete the user&amp;#x27;s notes when the user gets deleted. For that, we can use the &lt;code&gt;.cascade&lt;/code&gt; delete rule:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Model
class User {
    @Relationship(deleteRule: .cascade) var notes: [Note]?
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a &lt;code&gt;.cascade&lt;/code&gt; rule, all user related notes will be deleted whenever the user is deleted.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Getting started with Apollo iOS for GraphQL in Swift]]></title><description><![CDATA[Learn how to integrate GraphQL into a Swift application using Apollo iOS.]]></description><link>https://tanaschita.com/graphql-apollo-for-ios</link><guid isPermaLink="false">https://tanaschita.com/graphql-apollo-for-ios</guid><pubDate>Sun, 11 Feb 2024 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article on &lt;a target=&quot;_blank&quot; href=&quot;/graphql-essentials-for-ios/&quot;&gt;GraphQL essentials&lt;/a&gt;, we looked at what GraphQL is and how it differs from a REST approach.&lt;/p&gt;&lt;p&gt;Now, we are going to look at how we can implement a networking GraphQL layer by using Apollo in our iOS applications.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Apollo iOS is an open-source GraphQL framework for iOS written in Swift. We can use it to automatically generate Swift queries from a GraphQL scheme and GraphQl query files.&lt;/p&gt;&lt;p&gt;To begin using Apollo iOS, the following steps are recommended:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Adding Apollo as dependency&lt;/strong&gt; using the Swift Package Manager&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Installing Codegen CLI&lt;/strong&gt; provided by Apollo which helps with schema introspection and code generation.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuring code generation options&lt;/strong&gt; by creating the configuration file &lt;code&gt;apollo-codegen-config.json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Downloading a schema&lt;/strong&gt; from the GraphQL endpoint which Apollo needs to generate Swift types.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating queries&lt;/strong&gt; which will be converted to Swift types by Apollo.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execute queries and handle responses&lt;/strong&gt; by using pregenerated functions.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;h3&gt;1. Adding Apollo dependency&lt;/h3&gt;&lt;p&gt;Apollo offers the common options to install the library into an iOS project. For example by using the Swift Package Manager, we can add the following package: &lt;code&gt;https://github.com/apollographql/apollo-ios.git&lt;/code&gt;.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/fb482d923c7f859238e24c571ddba89e/2b78e/20231225-apollo-add-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:55.46666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB0UlEQVQoz22TyY6bQBRF+ZDutrEZDdhFFRjMJDPYBI+J3Ha6s4uUXf5/faN6xIMSL44KCXG49z2V0mQRujJHFkyRCg8p99AkAZbhDHNHg28MwM0huCEZEIGl0vsyYigjH3XMUUU+oZRJgK7KkYUzCNfEpuD4eW7RNA3qcnmnKpFwD4VwETka0pmFaOYh8hlC5iOYehATE0rMJpQsJ6GBhFmoFwxZwJAIjySE8BA6OpbhlFJdjnv8+nHC5+mELFthbqvUSPHtMZg1IvhEA7dUMO0FXH8FNx7QXxDaI3RFjDaPsC0ztGlINeWICu5AmEMoJLmhQ9gjBNbwP4Q5QOwZOHX1jWYhiHUa0hzlD+9Cewx/ovVCcwDxj5AbbyQ8b1c4b9f4vm/x+XWDj0OHj2OH901Di6LK3H4QWs+FMqFcxr7Osa8yOndVhs0ywZc8oqRyUZSwl16F6k0oJZJrwsW0T3jZrSmlfH7frijhZdcidvUnlaXw6Qzf6AMpk/XkDOUp+daWtKxkavWVHxPKLftyw3/T9fQJr5UPTYF93dfelil2REKbVoSjI3ANSsnsMeaugUOdI/cdMO31djvut2SE0B4T3FSxXvioljWi42+ExQ5/AIVWYcb+A71ZAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Adding Apollo package with SPM.&quot; title=&quot;Adding Apollo package with SPM.&quot; src=&quot;/static/fb482d923c7f859238e24c571ddba89e/2e9ed/20231225-apollo-add-package.png&quot; srcSet=&quot;/static/fb482d923c7f859238e24c571ddba89e/12d18/20231225-apollo-add-package.png 375w,/static/fb482d923c7f859238e24c571ddba89e/44507/20231225-apollo-add-package.png 750w,/static/fb482d923c7f859238e24c571ddba89e/2e9ed/20231225-apollo-add-package.png 1500w,/static/fb482d923c7f859238e24c571ddba89e/2b78e/20231225-apollo-add-package.png 2164w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Adding Apollo package with SPM&lt;/div&gt;&lt;p&gt;Within the dialog which offers different packages to install, only choose Apollo for now:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1308px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d96993a02e1ddae4ca2eb4d32b74c65b/da1d8/20231225-apollo-choose-package.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:45.33333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABh0lEQVQoz5WSy44aMRBF+xeymUUSwM3QL8j0wzb9xA0zEB49JJHyAVH2Uf5/eaI2NIMiZZHFlct2+eiWq5y1fqLb1BxNwbEted3UHFb5dV9wMAXLaEo6G5F5Y6Q/+acyX+AsQ5fjKufUlmyLDB26qEBg5AKTLWxiHYccmiUvRWrv+jP1F0wNwCwKMWbHs5wjvQnKm5A9jqifArZ5auMmDjk2ObtCon1hc+4lvTF5OEUGAkcFLrtqaV3uK031ySedfmQVR9ZVDzTJnPO6tlDlC+RsjJpNLqt3WfPwER1OcXQgONSa86Zhm2eYdG4TVknEyRQW2KYLvr4YOlNahwPo5nA2pojugJ9LSddWVv1jObuUua+1Bfbw13VlHQ8A5Q2lC6QnKOYeagDurw77sp51fOewtLFJF3zbGrq2vP1hLN6TuB9Q7gPp6B359AHtj3Bsh66d04FrR+Stc+ItDsQtr1c/FftaoZoTTfeT6vSDNFEXYD9f/6P+TWfnVZO035FffqPOv0iykj8Y7BeuOomKwgAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Choosing Apollo package.&quot; title=&quot;Choosing Apollo package.&quot; src=&quot;/static/d96993a02e1ddae4ca2eb4d32b74c65b/da1d8/20231225-apollo-choose-package.png&quot; srcSet=&quot;/static/d96993a02e1ddae4ca2eb4d32b74c65b/12d18/20231225-apollo-choose-package.png 375w,/static/d96993a02e1ddae4ca2eb4d32b74c65b/44507/20231225-apollo-choose-package.png 750w,/static/d96993a02e1ddae4ca2eb4d32b74c65b/da1d8/20231225-apollo-choose-package.png 1308w&quot; sizes=&quot;(max-width: 1308px) 100vw, 1308px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Choosing Apollo package.&lt;/div&gt;&lt;h3&gt;2. Installing Codegen CLI&lt;/h3&gt;&lt;p&gt;The Apollo iOS SPM package includes the Codegen CLI as an executable target. To install it, we can right-click on the project in Xcode&amp;#x27;s file explorer using the &lt;code&gt;Install CLI&lt;/code&gt; command:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/4a270/20231225-apollo-install-cli.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:53.6%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACCklEQVQoz2WSyXLaQBRF+RLHAwLNanU3EpoZDCIKGGxXYmeRRVapyj/k20+q2xhXKotbGko6fe99b/Tj1x+Or7/p+wdWq4FqXlGqmEpF5GlEJkJkOEV6N6TuDTJwyFVKJX3miUulQkrpo/w7pHfL6Hlb8/Vh4PkwMKw72kzYj2vpUSYTKjFlkQtaHTMLxujAQcchWTC2z1noWCnv1mq0Hza8Hnc89i37VcGmlFQqoNERVepSpR5dJmhURCE8smiCCj3S6TVi8snK3F+Ar6eB748Dx76jrzWt8mlUQKtD665OPe7rGbPQIXWvbSzpXluwcW5koktThwG+HDZ8Wdec+o7jprFqdUAjfQs011L4JM6VdWJgYmqADrtFYQ+7RPZvGf18OfDQLxiWc1bzxDo0INNfmTgUyZR1NWNbz1gUM2pt+ozsIe8xjbtL5NOmYX9f2/72y4K+0WdnHo10KYTPqspZ5oJmllCrmCLxyKMJeTy1eodZoPbvbGdmKKdty2PfsV+VrAth1emQdans2pgBmGutQrosQQfjj17PGpnJtVnM0+cluy6jUT516lLEDvNobDt8L904MAZM3G2bW7A8v1NnjWrhsVUhT13OsdbsspgydijFlCqZkCcehYzfekvfentbmat/1uUSuU1cDjLgW5tx0CG7eEJzHooBlsJFh45dYqOPn+/+gxngX+otYM/YXTHxAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Installing Codegen CLI.&quot; title=&quot;Installing Codegen CLI.&quot; src=&quot;/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/2e9ed/20231225-apollo-install-cli.png&quot; srcSet=&quot;/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/12d18/20231225-apollo-install-cli.png 375w,/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/44507/20231225-apollo-install-cli.png 750w,/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/2e9ed/20231225-apollo-install-cli.png 1500w,/static/ac9c5c5983b36d3fe0cabe81f1dd9a5b/4a270/20231225-apollo-install-cli.png 2212w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Installing Codegen CLI.&lt;/div&gt;&lt;p&gt;After installing the plugin, a symbolic link to the Codegen CLI is created in the project&amp;#x27;s root folder. We can now run the CLI from the command line with &lt;code&gt;./apollo-ios-cli&lt;/code&gt; which we&amp;#x27;ll use in later steps.&lt;/p&gt;&lt;p&gt;Note that apollo-ios-cli only works if the compiled CLI executable exists which is located in Xcode&amp;#x27;s Derived Data or .build folder. If these are cleared, we need to rerun the &lt;code&gt;Install CLI&lt;/code&gt; plugin to rebuild the executable.&lt;/p&gt;&lt;h3&gt;3. Configuring code generation options&lt;/h3&gt;&lt;p&gt;As a next step, we create a configuration file named &lt;code&gt;apollo-codegen-config.json&lt;/code&gt;. The Codegen CLI uses this file to configure the code generation engine.&lt;/p&gt;&lt;p&gt;We can use the Codegen Cli to create and prefill this file for us with default values by running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;./apollo-ios-cli init --schema-namespace ExampleAppAPI --module-type swiftPackageManager
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The command above generates the &lt;code&gt;apollo-codegen-config.json&lt;/code&gt; file in our project&amp;#x27;s root folder with the following content:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;schemaNamespace&amp;quot; : &amp;quot;ExampleAppAPI&amp;quot;,
  &amp;quot;input&amp;quot; : {
    &amp;quot;operationSearchPaths&amp;quot; : [
      &amp;quot;**/*.graphql&amp;quot;
    ],
    &amp;quot;schemaSearchPaths&amp;quot; : [
      &amp;quot;**/*.graphqls&amp;quot;
    ]
  },
  &amp;quot;output&amp;quot; : {
    &amp;quot;testMocks&amp;quot; : {
      &amp;quot;none&amp;quot; : {
      }
    },
    &amp;quot;schemaTypes&amp;quot; : {
      &amp;quot;path&amp;quot; : &amp;quot;./exampleAppAPI&amp;quot;,
      &amp;quot;moduleType&amp;quot; : {
        &amp;quot;swiftPackageManager&amp;quot; : {
        }
      }
    },
    &amp;quot;operations&amp;quot; : {
      &amp;quot;inSchemaModule&amp;quot; : {
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This default configuration will:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;find all GraphQL schema files ending with &lt;code&gt;.graphqls&lt;/code&gt; within our project directory.&lt;/li&gt;&lt;li&gt;find all GraphQL query files ending with &lt;code&gt;.graphql&lt;/code&gt; within our project directory.&lt;/li&gt;&lt;li&gt;generate Swift code for schema types and queries in a directory named &lt;code&gt;ExampleAppAPI&lt;/code&gt; when we use the CLI to generate code.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Before we can generate code, we need a GraphQL schema. Let&amp;#x27;s look at how to download it.&lt;/p&gt;&lt;h3&gt;4. Downloading a GraphQL schema&lt;/h3&gt;&lt;p&gt;A GraphQL schema serves as a contract between the client and the server, defining how clients can query or mutate data.&lt;/p&gt;&lt;p&gt;We can download the schema with the Codegen CLI by using the following command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;./apollo-ios-cli fetch-schema
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above command won&amp;#x27;t work yet. First, we need to add info to the configuration file about where to fetch the schema:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&amp;quot;schemaDownloadConfiguration&amp;quot;: {
    &amp;quot;downloadMethod&amp;quot;: {
        &amp;quot;introspection&amp;quot;: {
            &amp;quot;endpointURL&amp;quot;: &amp;quot;http://localhost:4000/graphql&amp;quot;,
            &amp;quot;httpMethod&amp;quot;: {
                &amp;quot;POST&amp;quot;: {}
            },
            &amp;quot;includeDeprecatedInputValues&amp;quot;: false,
            &amp;quot;outputFormat&amp;quot;: &amp;quot;SDL&amp;quot;
        }
    },
    &amp;quot;downloadTimeout&amp;quot;: 60,
    &amp;quot;headers&amp;quot;: [],
    &amp;quot;outputPath&amp;quot;: &amp;quot;./graphql/schema.graphqls&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, after running &lt;code&gt;fetch-schema&lt;/code&gt; we should see a graphql folder in the project directory containing a &lt;code&gt;schema.graphqls&lt;/code&gt; file.&lt;/p&gt;&lt;h3&gt;5. Creating queries&lt;/h3&gt;&lt;p&gt;Now, we can start creating queries, for example by copying them from the Apollo Sandbox. To add a query to an iOS project, we at first create a new file with a &lt;code&gt;.graphql&lt;/code&gt; extension and then paste the query into it, for example:&lt;/p&gt;&lt;p&gt;&lt;em&gt;RecipeFeedQuery.graphql&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;query RecipeFeed {
  recipeFeed {
    id
    imageUrl
    name
    creator {
      id
      profile {
        caption
        imageUrl
        name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;6. Executing queries and handle responses&lt;/h3&gt;&lt;p&gt;Finally, after building the project, we can now execute the query by using functions Apollo generated for us:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import Apollo
import ExampleAppAPI


let apolloClient = ApolloClient(url: endpointUrl)
apolloClient.fetch(query: RecipeFeedQuery()) { result in
    switch result {
    case .success(let response):
        print(response.data?.recipeFeed)
    case .failure(let error):
        print(error)
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>graphql</category><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Get started with auto‑renewable subscriptions for iOS]]></title><description><![CDATA[Learn how to implement in-app purchase subscriptions with StoreKit and SwiftUI.]]></description><link>https://tanaschita.com/20231211-auto-renewable-subscriptions</link><guid isPermaLink="false">https://tanaschita.com/20231211-auto-renewable-subscriptions</guid><pubDate>Sun, 10 Dec 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple provides 4 different types of in-app purchases that allow us to offer revenue generating content and features to users:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Consumable&lt;/strong&gt; for recurring purchases that users can consume.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Non-consumable&lt;/strong&gt; for one-time purchases that don’t expire.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Auto‑renewable subscriptions&lt;/strong&gt; for subscriptions that renew automatically at the end of each subscription period until the user cancels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Non-renewing subscriptions&lt;/strong&gt; for subscriptions that expire after a fixed duration and require the user to manually renew them.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In this article, we&amp;#x27;ll take a closer look at auto‑renewable subscriptions, on how they work and how we can implement them using StoreKit and SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Overview&lt;/h3&gt;&lt;p&gt;Auto‑renewable subscriptions allow users to pay for access to premium features or content on a recurring basis, typically on a monthly or yearly basis. They are perfect for apps that offer ongoing access to premium content, services or features.&lt;/p&gt;&lt;p&gt;To implement auto‑renewable subscriptions, the following basic steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Configure subscriptions in App Store Connect or through a local StoreKit configuration file for testing.&lt;/li&gt;&lt;li&gt;Load and show subscriptions options in the iOS application.&lt;/li&gt;&lt;li&gt;Unlock content when a user subscribes.&lt;/li&gt;&lt;li&gt;Add support for restoring in-app purchases.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;h3&gt;1. Configure auto‑renewable subscriptions&lt;/h3&gt;&lt;p&gt;To configure suscriptions through App Store Connect, we need an Apple Developer Account and a configured app in App Store Connect. For testing purposes, we can alternatively setup a StoreKit configuration file locally in Xcode which we&amp;#x27;ll do in this article. To learn more about StoreKit configuration files, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20230904-storekit-testing-in-xcode/&quot;&gt;StoreKit Testing in Xcode&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Every subscription is part of a group. Users can only subscribe to one subscription of the same group at a time, but can change to another subscription in the same group. When adding our first auto-renewable subscription, we&amp;#x27;ll be asked to enter a group name.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/99286f1b5cf432db1128c6a5e8d4c7c5/5ddef/20231102-auto-renewable-suscriptions.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:31.2%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABOElEQVQY012Q3U6DQBCFeYgmNGkssD8UloVCC2WptT9AQ6MmjV4Yo/HGC/X9X+CYnRovenEys5ucM9+MU2iJTZ2jKTPUhUI68yE8F4L5KPI5yuWCtKpKJCpGHIVIdYI4mkEKjlCKfwnO4LDqjO35C+3TN+btO6R5xiQ7wRcReOCBMwYW+GRYFDmFb+82MPWK3iqOKFzFdgCDM8pfIHefyIYfTOoPjIpXuMs3eFKDB1MKs5OtyRI2psZ+t4WpK6wbQ8G2N8YgUhqO8Mbg0zHYjQvbS28MMXUpzNJxFlDgPEtp7dt1g9MwoD3s0bUHdF2L3tZ+gMpXcBiZLmJ/CoIAnDO6YVUuaS3fm2IWSqLMUg2dKLolKVHQWkOGERy7kqWw9bq//rNmS/j4cE90x75D33U4dgf0xwvhL02tuAAOx7RPAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Auto renewable subscritions setup example.&quot; title=&quot;Auto renewable subscritions setup example.&quot; src=&quot;/static/99286f1b5cf432db1128c6a5e8d4c7c5/2e9ed/20231102-auto-renewable-suscriptions.png&quot; srcSet=&quot;/static/99286f1b5cf432db1128c6a5e8d4c7c5/12d18/20231102-auto-renewable-suscriptions.png 375w,/static/99286f1b5cf432db1128c6a5e8d4c7c5/44507/20231102-auto-renewable-suscriptions.png 750w,/static/99286f1b5cf432db1128c6a5e8d4c7c5/2e9ed/20231102-auto-renewable-suscriptions.png 1500w,/static/99286f1b5cf432db1128c6a5e8d4c7c5/5ddef/20231102-auto-renewable-suscriptions.png 1786w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;p&gt;The setup above shows a subscription group named &lt;code&gt;subscriptionOptions&lt;/code&gt; which contains two different subscription options: a &lt;code&gt;premium.yearly&lt;/code&gt; and a &lt;code&gt;premium.monthly&lt;/code&gt; subscription.&lt;/p&gt;&lt;p&gt;For every subscription option, we can setup a price, duration, free trial periods and more. To learn more on how to setup and display a free trial period, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20231113-subscriptions-introductory-offers/&quot;&gt;introductory offers in StoreKit 2&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;2. Load and show subscriptions options in the iOS application&lt;/h3&gt;&lt;p&gt;After setting up subscrition options, we can load and show subscriptions options in our iOS application. In StoreKit 2, a subscription is represented by the &lt;code&gt;Product&lt;/code&gt; struct. We can load products by calling its &lt;code&gt;products(for:)&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let subscriptions = try await Product.products(for: [&amp;quot;premium.yearly&amp;quot;, &amp;quot;premium.monthly&amp;quot;])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each product has properties which we can use to display subscription infos to the user. For example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;displayPrice&lt;/code&gt; which is a localized string representation of the subscription price&lt;/li&gt;&lt;li&gt;&lt;code&gt;displayName&lt;/code&gt; which is a localized display name of the subscription&lt;/li&gt;&lt;li&gt;&lt;code&gt;subscription.subscriptionPeriod.unit&lt;/code&gt; which we can use to display the period info for the subscription&lt;/li&gt;&lt;li&gt;&lt;code&gt;subscription.introductoryOffer&lt;/code&gt; which we can use to display introductory offers such as free trials&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;3. Unlock content when users subscribe&lt;/h3&gt;&lt;p&gt;When a user decides to subscribe to one of the offered subscriptions, we can use the &lt;code&gt;purchase&lt;/code&gt; method on the selected product:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let result = try await product.purchase()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This call will trigger Apple&amp;#x27;s native purchasing dialog providing us with the result after the user completes:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;switch result {
case .success(let result):
    switch verificationResult {
    case .verified(let transaction):
        // Give the user access to purchased content.
        // Complete the transaction with transaction.finish()
    case .unverified(let transaction, let verificationError):
        // The purchase failed verification for the provided reason.
    }
case .pending:
    // The purchase is waiting for some user action.
    // Purchase may succeed in the future,
    // the resulting Transaction will be delivered via Transaction.updates
case .userCancelled:
    // The user cancelled the purchase.
default:
    break
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4. Add support for restoring in-app purchases&lt;/h3&gt;&lt;p&gt;When users reinstall our iOS app or download it on a new device, we want to make sure all in-app purchases the user previously made are restored. Restoring subscriptions works the same way as for any other StoreKit 2 products. Check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20231009-restore-in-app-purchases-storekit/&quot;&gt;how to restore in-app purchases with StoreKit 2&lt;/a&gt; to learn more.&lt;/p&gt;</content:encoded><category>storekit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Introduction to Kotlin for Swift developers - protocols, extensions & generics]]></title><description><![CDATA[Learn how protocols, extensions and generics in Kotlin work from a Swift developer's point of view.]]></description><link>https://tanaschita.com/kotlin-for-swift-developers-part-3</link><guid isPermaLink="false">https://tanaschita.com/kotlin-for-swift-developers-part-3</guid><pubDate>Fri, 01 Dec 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a target=&quot;_blank&quot; href=&quot;/kotlin-for-swift-developers-part-1/&quot;&gt;first part&lt;/a&gt; of Kotlin for Swift developers, we looked at the basics of Kotlin like variables, functions &amp;amp; control flow.&lt;/p&gt;&lt;p&gt;In the &lt;a target=&quot;_blank&quot; href=&quot;/kotlin-for-swift-developers-part-2/&quot;&gt;second part&lt;/a&gt;, we looked at classes, structs and enums and how those are similar or differentiate from Swift.&lt;/p&gt;&lt;p&gt;In this part, we&amp;#x27;ll do the same for protocols, extensions and generics.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Protocols&lt;/h3&gt;&lt;p&gt;As an equivalent to Swift protocols, Kotlin provides interfaces. Like Swift&amp;#x27;s protocols, interfaces in Kotlin can contain declarations of abstract methods and properties as well as method implementations, but cannot store state.&lt;/p&gt;&lt;p&gt;Here is an example of an interface in Kotlin:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;interface Purchasable {

    var price: BigDecimal

    fun purchase()
    fun newUserDiscount(): BigDecimal {
        return price - ...
    }
}

class Chocolate : Purchasable {

    var price: BigDecimal = BigDecimal(&amp;quot;3.99&amp;quot;)

    override fun purchase() {
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, we can provide a default method implementation when working with Kotlin interfaces. In Swift, we can achieve the same with protocol extensions.&lt;/p&gt;&lt;h3&gt;Extensions&lt;/h3&gt;&lt;p&gt;Similar to Swift, Kotlin provides extensions that allow us to extend a class or an interface with new functionality. An example of an extension function in Kotlin might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;fun Animal.run() {
    println(&amp;quot;Running&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The equivalent in Swift looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;extension Animal {
    func run() {
        print(&amp;quot;Running&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similar to Swift, extensions in Kotlin cannot have stored properties, but computer properties are allowed:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;val Animal.description: String
    get() = &amp;quot;name: $name, age: $age&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Generics&lt;/h3&gt;&lt;p&gt;Generics are a powerful feature in programming languages that enable us developers to write flexible and reusable code.&lt;/p&gt;&lt;p&gt;Similar to Swift, Kotlin provides the possibility to create generic functions and generic types, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class Stack&amp;lt;Element&amp;gt;(initialItems: Array&amp;lt;Element&amp;gt;) {
    private var items: Array&amp;lt;Element&amp;gt; = initialItems
}

val stack = Stack&amp;lt;Int&amp;gt;(intArrayOf(1, 2, 3))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Swift, the same example looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class Stack&amp;lt;Element&amp;gt; {
    private var items: [Element] = []

    init(items: [Element]) {
        self.items = items
    }
}

let stack = Stack&amp;lt;Int&amp;gt;(items: [1, 2, 3])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, there are almost no differences when defining generic types.&lt;/p&gt;&lt;p&gt;The same applies to generic functions - which look as follows in Kotlin:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;fun &amp;lt;Element&amp;gt; add(item: Element) {
}

fun &amp;lt;Element: SomeInterface&amp;gt; add(item: Element) {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Swift:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func add&amp;lt;Element&amp;gt;(item: Element) {
}

func add&amp;lt;Element: SomeProtocol&amp;gt;(item: Element) {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see, the only difference is the position of the placeholder type name.&lt;/p&gt;</content:encoded><category>beyond ios</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to store images in SwiftData]]></title><description><![CDATA[Learn how to efficiently store larger amounts of data in SwiftData.]]></description><link>https://tanaschita.com/swiftdata-store-images</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-store-images</guid><pubDate>Sun, 26 Nov 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftData allows us to store images by using the &lt;code&gt;Data&lt;/code&gt; type, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Model
class Recipe {
    var imageData: Data
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working with larger or many images, it might be a good idea to separate them from the model storage and store them in an external file. For that, SwiftData provides the &lt;code&gt;.externalStorage&lt;/code&gt; attribute:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Attribute(.externalStorage)
var imageData: Data?
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that in place, we tell SwiftData to store the image data in external storage managed by SwiftData, which may lead to improved performance when working with larger amounts of data.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to migrate to a new schema with SwiftData in iOS]]></title><description><![CDATA[Learn how to create a migration plan in SwiftData.]]></description><link>https://tanaschita.com/swiftdata-schema-migration</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-schema-migration</guid><pubDate>Sun, 19 Nov 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Whenever we release a new version of our app where we made changes to our SwiftData models, we need to make sure that SwiftData is able to migrate between the old and the new schema to prevent data loss.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and look at how to create a migration plan in SwiftData.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The basic steps of a migration with SwiftData are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Use &lt;code&gt;VersionedSchema&lt;/code&gt; to create versions of SwiftData models&lt;/li&gt;&lt;li&gt;Use &lt;code&gt;SchemaMigrationPlan&lt;/code&gt; to order the created versions&lt;/li&gt;&lt;li&gt;Define a migration stage for each migration - which can be lightweight or custom&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;h3&gt;1. Use &lt;code&gt;VersionedSchema&lt;/code&gt; to create versions of SwiftData models&lt;/h3&gt;&lt;p&gt;If we want to make a change to a SwiftData model, the first thing to do is to create a schema version for the old model. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UserSchemaV1: VersionedSchema {
    static var versionIdentifier = Schema.Version(1, 0, 0)

    static var models: [any PersistentModel.Type] {
        return [User.self]
    }

    @Model
    class User {
        @Attribute(.unique) var id: String
        var validated: Bool
        ...
    }
}

enum UserSchemaV2: VersionedSchema {
    static var versionIdentifier = Schema.Version(2, 0, 0)

    ...

    @Model
    class User {
        @Attribute(.unique) var id: String
        @Attribute(originalName: &amp;quot;validated&amp;quot;) var isValidated: Bool
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above example shows two schema versions each containing the model classes they define. In the second version, we renamed the &lt;code&gt;validated&lt;/code&gt; property to &lt;code&gt;isValidated&lt;/code&gt; and marked it with an appropriate attribute. With that in place, SwiftData can migrate those two versions with a lightweight migration.&lt;/p&gt;&lt;h3&gt;2. Use &lt;code&gt;SchemaMigrationPlan&lt;/code&gt; to order the created versions&lt;/h3&gt;&lt;p&gt;After defining a new version, we need to create a migration plan which describes how to handle the migrations from version to version:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UsersMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [UserSchemaV1.self, UserSchemaV2.self]
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With &lt;code&gt;schemas&lt;/code&gt;, we tell the migration plan in which order the migration should be performed.&lt;/p&gt;&lt;h3&gt;3. Define the migration stage for each migration&lt;/h3&gt;&lt;p&gt;With that in place, we now can define the migration stage for each migration. A migration stage can be a lightweight or custom.&lt;/p&gt;&lt;p&gt;For a specific set of changes, SwiftData can perform a lightweight migration. Examples of those changes are adding, renaming or deleting entities, attributes or relationships, changing the relationship type and more.&lt;/p&gt;&lt;p&gt;Since we only renamed an attribute in our example and already annotated it, we can use the lightweight migration:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum UsersMigrationPlan: SchemaMigrationPlan {
    static var schemas: [any VersionedSchema.Type] {
        [UserSchemaV1.self, UserSchemaV2.self]
    }

    static var stages: [MigrationStage] {
        [migrateV1toV2]
    }

    static let migrateV1toV2 = MigrationStage.lightweight(
        fromVersion: UserSchemaV1.self,
        toVersion: UserSchemaV2.self
    )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When changes exceed the capabilities of a lightweight migration, we need to do a custom migration which can look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;static let migrateV1toV2 = MigrationStage.custom(
    fromVersion: UserSchemaV1.self,
    toVersion: UserSchemaV2.self,
    willMigrate: { context in
        // Custom steps before the migration begins
    },
    didMigrate: { context in
        // Custom steps after the migration
    }
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the custom stage provides us with two closures, which we can use to perform an operation before or after the data is migrated.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>persistence</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to implemet a free trial period for StoreKit 2 subscriptions in iOS]]></title><description><![CDATA[Learn how to implement StoreKit 2 introductory offers.]]></description><link>https://tanaschita.com/20231113-subscriptions-introductory-offers</link><guid isPermaLink="false">https://tanaschita.com/20231113-subscriptions-introductory-offers</guid><pubDate>Sun, 12 Nov 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When setting up auto-renewable subscriptions for an iOS application with StoreKit 2, we might want to offer users a free trial period.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Setting up a subscription with a free trial&lt;/h3&gt;&lt;p&gt;When setting up a StoreKit subscription in App Store Connect or via a StoreKit test file, we have the possibility to setup an introductory offer.&lt;/p&gt;&lt;undefined&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1332px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/abbde2ac00506442b8008edb4ae594e2/ea696/20231102-introductary-offers.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:24.53333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA00lEQVQY02WP/XKCMBDEeZKaBEgCJBA+lWIptqMz6vs/z3buUEftH79k7zLZ2Y2UFBCbjwc0p0mMpg5M19TYDT1KXyBUJULl3yjh6gFKSsRKItJpgr5rsR16xhrND7RPlETXBhyWGdO4hS8y+MLe7gyOdQ6jU/7DhnRYY5DZlSRWkGLD2ruCjU7HX3yOA0Lp/kMJq5abxVIgIkEGd2gmjNacfBp3uF7OmL/2WJb5le8ZP4cF9XSEFGJNyM63uM/QnupTUpdnyK19tHgmtwZaa9x9/gB295Y8vfWTbwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Introductary offers&quot; title=&quot;Introductary offers&quot; src=&quot;/static/abbde2ac00506442b8008edb4ae594e2/ea696/20231102-introductary-offers.png&quot; srcSet=&quot;/static/abbde2ac00506442b8008edb4ae594e2/12d18/20231102-introductary-offers.png 375w,/static/abbde2ac00506442b8008edb4ae594e2/44507/20231102-introductary-offers.png 750w,/static/abbde2ac00506442b8008edb4ae594e2/ea696/20231102-introductary-offers.png 1332w&quot; sizes=&quot;(max-width: 1332px) 100vw, 1332px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Introductary offers.&lt;/div&gt;&lt;/undefined&gt;&lt;p&gt;As shown above, we can choose between 3 options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Pay as you go&lt;/strong&gt; - new subscribers pay an introductory price each billing period for a specific duration, for example 0.99 € per month for 3 months. After 3 months, subscribers pay the regular monthly price.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pay up front&lt;/strong&gt; - new subscribers pay a one-time introductory price for a specific duration, for example 0.99 € for 1 month. After 1 month, subscribers pay the regular monthly price.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Free trial&lt;/strong&gt; - new subscribers access content for free for a specified duration. After the free trial period is over, they pay the regular price.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;In our case, we choose the free trial option and select the duration of the free trial period.&lt;/p&gt;&lt;h3&gt;Showing the free trial options in the iOS app&lt;/h3&gt;&lt;p&gt;After setting up the free trial, the next step is to show it to eligible users. A user is not eligible if they already used an introductary offer of that subscription or a subscription of the same group before.&lt;/p&gt;&lt;p&gt;We can use the subscription&amp;#x27;s &lt;code&gt;introductoryOffer&lt;/code&gt; property that will automatically be applied if the user is eligible. Alternatively, we can use the &lt;code&gt;isEligibleForIntroOffer&lt;/code&gt; property on the subscription to check if the user is eligible:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func isEligibleForFreeTrial() async -&amp;gt; Bool {
    guard let subscription = product?.subscription else { return false }
    return await subscription.isEligibleForIntroOffer
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The subscription&amp;#x27;s &lt;code&gt;introductoryOffer&lt;/code&gt; offers different properties which represent the information we configured, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;introductoryOffer.paymentMode&lt;/code&gt; represents the configured payment option which may be &lt;code&gt;payAsYouGo&lt;/code&gt;, &lt;code&gt;payUpFront&lt;/code&gt; or &lt;code&gt;freeTrial&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;introductoryOffer.period&lt;/code&gt; represents the period of the introductary offer&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If we decide to offer another option such as &lt;code&gt;payAsYouGo&lt;/code&gt; or &lt;code&gt;payUpFront&lt;/code&gt;, we can also use properties like &lt;code&gt;displayPrice&lt;/code&gt; or &lt;code&gt;periodCount&lt;/code&gt; to show relevant information to the user.&lt;/p&gt;</content:encoded><category>storekit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to get a SwiftData model container and context in SwiftUI]]></title><description><![CDATA[Learn how to setup a SwiftData container and context directly in SwiftUI.]]></description><link>https://tanaschita.com/swiftdata-model-container-context-swiftui</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-model-container-context-swiftui</guid><pubDate>Sun, 29 Oct 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC23, Apple introduced SwiftData - a framework which focuses on persisting data by using declarative code. In this &lt;a target=&quot;_blank&quot; href=&quot;/swiftdata-quick-developer-guide/&quot;&gt;overview on SwiftData for iOS&lt;/a&gt;, we already looked at the basics of SwiftData.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at view modifiers which we can use to setup a SwiftData container and context directly in SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;In the previous article, we already learned on how to setup a SwiftData model container:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let modelContainer = try ModelContainer(for: User.self)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working with SwiftUI, we can alternatively use the &lt;code&gt;modelContainer&lt;/code&gt; view modifier as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ExampleApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [User.self])
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example above creates a container for the whole window group scene. The window and its views will inherit the container.&lt;/p&gt;&lt;p&gt;If a view needs its own container, we can setup one in the same way by applying the &lt;code&gt;modelContainer&lt;/code&gt; modifier to a specific view. Changes to one container won&amp;#x27;t affect another container.&lt;/p&gt;&lt;p&gt;When we setup the model container through the view modifier as shown above, it makes the model context available through the application&amp;#x27;s environment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Environment(\.modelContext) private var context
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use the environment to get the model context in any SwiftUI view we need.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>persistence</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Introduction to bash scripting for iOS developers]]></title><description><![CDATA[Learn how to use bash scripting for iOS development.]]></description><link>https://tanaschita.com/20231023-bash-scripting-for-ios-devs</link><guid isPermaLink="false">https://tanaschita.com/20231023-bash-scripting-for-ios-devs</guid><pubDate>Sun, 22 Oct 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Bash (or shell) scripting provides a powerful way to automate repetitive tasks and can save us developers a lot of time.&lt;/p&gt;&lt;p&gt;Using Bash scripting as an iOS developer can help perform a wide range of tasks such as automating build processes, managing file systems, running tests or and deploying applications. Any command we can run in the terminal can be run in a Bash script.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at the basics of Bash scripting and a practical example for iOS development.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is Bash?&lt;/h3&gt;&lt;p&gt;Bash, short for Bourne Again Shell, is a command-line shell and scripting language widely used in Unix and Linux environments. It provides a way to interact with the operating system by executing commands and scripts.&lt;/p&gt;&lt;h3&gt;Writing and running your first Bash script&lt;/h3&gt;&lt;p&gt;Every Bash script file starts with &lt;code&gt;#!/bin/bash&lt;/code&gt; on its own line. This way, the computer knows which type of interpreter to use for the script.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
echo &amp;quot;Hello world!&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can save the code above in a file with a &lt;code&gt;.sh&lt;/code&gt; extension, for example &lt;code&gt;test.sh&lt;/code&gt; and then try and run it in the terminal by simply entering the path to the script:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/path/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A script file needs to have permission to be executed. In case of a permission error, run the following command:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod +x /path/test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After running the script again, the &lt;code&gt;Hello world!&lt;/code&gt; greeting should now be printed out in the terminal.&lt;/p&gt;&lt;h3&gt;Basic scripting concepts&lt;/h3&gt;&lt;p&gt;While Bash is primarily a scripting language, it incorporates many programming language elements. Here are some of the key ones:&lt;/p&gt;&lt;h4&gt;1. Variables&lt;/h4&gt;&lt;p&gt;In Bash scripting, variables are declared and used as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
hello=&amp;quot;Hello to you!&amp;quot;
echo $hello
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown above, when accessing a value of a variable, we always prefix it with a &lt;code&gt;$&lt;/code&gt; sign.&lt;/p&gt;&lt;h4&gt;2. Conditionals&lt;/h4&gt;&lt;p&gt;Bash scripting supports if-else conditions:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;if [ &amp;quot;$input&amp;quot; == &amp;quot;$name&amp;quot; ]
then
  echo &amp;quot;yes&amp;quot;
else
  echo &amp;quot;no&amp;quot;
fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working strings, it&amp;#x27;s best practice to put the variable into quotes to prevent errors when the variable contains spaces.&lt;/p&gt;&lt;h4&gt;3. Loops&lt;/h4&gt;&lt;p&gt;Bash scripts support &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt; and &lt;code&gt;until&lt;/code&gt; loops. Let&amp;#x27;s look at a for-loop example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for name in 1 2 3 4 5
do
  echo $name
done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively to the sequence, we could setup a range &lt;code&gt;{1..5}&lt;/code&gt; to achieve the same results.&lt;/p&gt;&lt;h4&gt;4. Functions&lt;/h4&gt;&lt;p&gt;Bash also supports functions:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;function greet() {
    echo &amp;quot;Hello, $1!&amp;quot;
}

greet &amp;quot;Alice&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Function arguments are not defined as part of a function but accessed using &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt; etc.&lt;/p&gt;&lt;p&gt;There is more to discover like &lt;strong&gt;working with arrays and dictionaries, numeric calculations, error handling, file handling&lt;/strong&gt; and more which is not covered by this article. Checkout this &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://devhints.io/bash&quot;&gt;cheatsheet on Bash scripting&lt;/a&gt; to see more examples.&lt;/p&gt;&lt;h3&gt;Handling user input&lt;/h3&gt;&lt;p&gt;When writing a bash script, we often need some kind of outside input. A common way is to use input arguments that are entered when calling a script separated by spaces:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;/path/test.sh argument1 argument2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Within the script, the arguments are accessed using &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt; etc.&lt;/p&gt;&lt;p&gt;Another possibility is to request user input with &lt;code&gt;read&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;echo &amp;quot;What&amp;#x27;s your name?&amp;quot;
read userInput
echo $userInput
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Setup an alias&lt;/h3&gt;&lt;p&gt;When working with a script, we can setup an alias so we don&amp;#x27;t need to use the full path every time we call it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;alias test=&amp;#x27;/path/test.sh&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can call the script by using the alias:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;testscript argument1 argument2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a permanent alias, we can set it up in the &lt;code&gt;.bash_profile&lt;/code&gt; file which is a hidden configuration file in the home directory.&lt;/p&gt;&lt;h3&gt;Bash scripting for iOS example&lt;/h3&gt;&lt;p&gt;Finally, let&amp;#x27;s look at a practical example on how we could use bash scripting for iOS development.&lt;/p&gt;&lt;p&gt;The following script automates taking screenshots of an iOS application for different devices and languages:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

// 1.
projectName=&amp;quot;./exampleApp.xcodeproj&amp;quot;
schemeName=&amp;quot;exampleApp&amp;quot;
simulators=(&amp;quot;iPhone 14&amp;quot; &amp;quot;iPhone 13&amp;quot;)
languages=(&amp;quot;en&amp;quot; &amp;quot;de&amp;quot;)
appearances=(&amp;quot;light&amp;quot; &amp;quot;dark&amp;quot;)

// 2.
targetFolder=$1

// 3.
for simulator in &amp;quot;${simulators[@]}&amp;quot;
do
    for language in &amp;quot;${languages[@]}&amp;quot;
    do
        for appearance in &amp;quot;${appearances[@]}&amp;quot;
        do
            // 4.
            xcrun simctl boot &amp;quot;$simulator&amp;quot;
            xcrun simctl ui &amp;quot;$simulator&amp;quot; appearance $appearance

            xcodebuild -testLanguage $language -scheme $schemeName -project $projectName -destination &amp;quot;platform=iOS Simulator,name=$simulator&amp;quot; build test

            xcparse screenshots /path/to/xcresult &amp;quot;$targetFolder/$simulator/$language/$appearance&amp;quot; \;
        done
    done
done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s go through it step-by-step:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Defining variables that list devices, languages and appearance modes we&amp;#x27;ll use to take screenshots.&lt;/li&gt;&lt;li&gt;Defining the &lt;code&gt;targetFolder&lt;/code&gt; variable and assigning it the first user input argument.&lt;/li&gt;&lt;li&gt;Looping through previously defined variables.&lt;/li&gt;&lt;li&gt;Using xcrun, xcodebuild and xcparse command line tools to start a simulator, run tests and to extract screenshots that were taken while running the tests.&lt;/li&gt;&lt;/ol&gt;</content:encoded><category>command line</category><category>ios</category></item><item><title><![CDATA[Quick developer guide on SwiftData for iOS]]></title><description><![CDATA[Get an overview on Apple's SwiftData framework.]]></description><link>https://tanaschita.com/swiftdata-quick-developer-guide</link><guid isPermaLink="false">https://tanaschita.com/swiftdata-quick-developer-guide</guid><pubDate>Sun, 15 Oct 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC23, Apple introduced SwiftData - a framework which focuses on persisting data by using declarative code. SwiftData uses Swift&amp;#x27;s new macro system and is designed to work with SwiftUI.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump in and look at the basics on how SwiftData works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Defining a schema with the &lt;code&gt;@Model&lt;/code&gt; macro&lt;/h3&gt;&lt;p&gt;The first step when trying to persist data in a data base is to define a model schema - which is a blueprint that outlines the structure and relationships within a database.&lt;/p&gt;&lt;p&gt;SwiftData introduces the &lt;code&gt;@Model&lt;/code&gt; macro which lets us define our models directly with Swift. Let&amp;#x27;s look at a simple example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Model
class User {
    var name: String
    var notes: [Note]

    init(name: String, notes: [Note] = []) {
        self.name = name
        self.notes = notes
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When defining models with SwiftData, we can use all basic types as properties out of the box. We can also use custom types like structs and enums that conform to the &lt;code&gt;Codable&lt;/code&gt; protocol.&lt;/p&gt;&lt;p&gt;SwiftData provides different ways to add meta data to properties which gives us control on how they are persisted. For example, we can use the &lt;code&gt;@Attribute&lt;/code&gt; macro to add a uniqueness constraint to a property:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class User {
    ...
    @Attribute(.unique) var id: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;By default, SwiftData persists all non-computed attributes. In case we want to exclude properties from persisting, we can use the &lt;code&gt;@Transient&lt;/code&gt; macro:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class User {
    @Transient var isValidated: Bool
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To specify a relationship between two model classes, SwiftData provides the &lt;code&gt;@Relationship&lt;/code&gt; macro. Check out this article to learn more about &lt;a href=&quot;/swiftdata-one-to-many-relationships/&quot;&gt;relationships in SwiftData&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Model container&lt;/h3&gt;&lt;p&gt;SwiftData&amp;#x27;s model container represents the persistence backend which we can use to customize persistence behaviour or to control migration. To use SwiftData, an app needs to setup at least one model container.&lt;/p&gt;&lt;p&gt;We can setup a model container as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let modelContainer = try ModelContainer(for: User.self)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When initializing a model container, we pass in all models we&amp;#x27;d like to store.&lt;/p&gt;&lt;p&gt;If needed, we can additionally pass in a configuration, for example to use a certain storage location or to save the data in memory by using &lt;code&gt;ModelConfiguration&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: schema, configurations: [configuration])
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Model context&lt;/h3&gt;&lt;p&gt;SwiftData&amp;#x27;s model context allows us to track updates, fetch models, save or undo changes.&lt;/p&gt;&lt;p&gt;We can create a model context as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let context = ModelContext(container)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we can use it to execute an action, for example to save a user:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;context.insert(user)
try context.save()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively to creating a model container as shown above, SwiftUI provides a view modifier to setup a model container on a SwiftUI view and use the environment to get the model context. Check out this article on &lt;a href=&quot;/swiftdata-model-container-context-swiftui/&quot;&gt;how to use SwiftData view modifiers with SwiftUI&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;Fetching data&lt;/h3&gt;&lt;p&gt;To get stored data, we can create a fetch descriptor and use it to fetch the data we need. For example a simple request to fetch all users might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let fetchDescriptor = FetchDescriptor&amp;lt;User&amp;gt;()
let users = try modelContext.fetch(fetchDescriptor)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally, we can pass in a predicate and a sort descriptor to filter and sort the results:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let descriptor = FetchDescriptor&amp;lt;User&amp;gt;(
                predicate: predicate,
                sortBy: sortDescriptor)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Swift provides a new fully type checked &lt;code&gt;Predicate&lt;/code&gt; type for fetching data which is a replacement for &lt;code&gt;NSPredicate&lt;/code&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let predicate = #Predicate&amp;lt;User&amp;gt; {
    $0.id == someId
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For SwiftUI, SwiftData additionally provides the &lt;code&gt;@Query&lt;/code&gt; property wrapper which provides the view with the data.&lt;/p&gt;&lt;h3&gt;Migration&lt;/h3&gt;&lt;p&gt;When working with persistence, migration is often needed as soon as the schema changes. For example, if we need to rename some properties, change relationships and more.&lt;/p&gt;&lt;p&gt;SwiftData provides a &lt;code&gt;VersionedSchema&lt;/code&gt; and &lt;code&gt;SchemaMigrationPlan&lt;/code&gt; to define migration steps. Check out this article on &lt;a href=&quot;/swiftdata-schema-migration/&quot;&gt;how to migrate to a new schema with SwiftData&lt;/a&gt; to learn more.&lt;/p&gt;</content:encoded><category>swiftdata</category><category>persistence</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to restore in-app purchases with StoreKit 2 for iOS]]></title><description><![CDATA[Learn how to restore in-app purchases proactively and with a restore purchases button.]]></description><link>https://tanaschita.com/20231009-restore-in-app-purchases-storekit</link><guid isPermaLink="false">https://tanaschita.com/20231009-restore-in-app-purchases-storekit</guid><pubDate>Sun, 08 Oct 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When it comes to handling in-app-purchases in an app, we as developers need to make sure that as soon as users reinstall our iOS app or download it on a new device, all in-app purchases the user previously made are restored.&lt;/p&gt;&lt;p&gt;The best practice is to implement the following two options:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Restoring in-app purchases proactively, without the user needing to take any actions.&lt;/li&gt;&lt;li&gt;Providing a restore purchases button which users can use when they suspect that the app does not show all the transactions.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;p&gt;If you need to refresh your knowledge on StoreKit 2, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20231002-storekit-2-overview/&quot;&gt;getting started with StoreKit 2 for iOS&lt;/a&gt;.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Restoring in-app purchases proactively&lt;/h3&gt;&lt;p&gt;StoreKit automatically keeps up to date transaction information and subscription status available in our app, even when users reinstall it or download it on a new device.&lt;/p&gt;&lt;p&gt;This means that when our app launches, all transactions are automatically available so we can use current entitlements to restore all in-app purchases as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func refreshPurchasedProducts() async {
    for await result in Transaction.currentEntitlements {
        guard case .verified(let transaction) = result else { continue }
        if transaction.revocationDate == nil {
            self.purchasedProductIDs.insert(transaction.productID)
        } else {
            self.purchasedProductIDs.remove(transaction.productID)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Providing a restore purchases button&lt;/h3&gt;&lt;p&gt;In case that the in-app purchases cannot be automatically restored, for example because the user is not logged in with the App Store, we should provide a restore purchases button which calls &lt;code&gt;AppStore.sync()&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Button {
    Task {
        do {
            try await AppStore.sync()
        } catch {
            print(error)
        }
    }
} label: {
    Text(&amp;quot;Restore purchases&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Calling &lt;code&gt;AppStore.sync()&lt;/code&gt; forces the app to obtain transaction information and subscription status from the App Store. It may display a system prompt that asks users to authenticate with their App Store credentials. Apple advices to only call this function in response to an explicit user action, like tapping a button.&lt;/p&gt;</content:encoded><category>storekit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Get started with StoreKit 2 for iOS]]></title><description><![CDATA[Learn how to implement in-app purchases and subscriptions with StoreKit 2.]]></description><link>https://tanaschita.com/20231002-storekit-2-overview</link><guid isPermaLink="false">https://tanaschita.com/20231002-storekit-2-overview</guid><pubDate>Sun, 01 Oct 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC21, Apple introduced a new version of StoreKit - StoreKit 2. The new in-app purchases API offers new features and improvements over the previous version, including async/await adaptation, more flexible payment options, improved testing and debugging tools.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll go though the StoreKit 2 API and learn how to use it to request in-app purchase products, how to purchase and validate them, how to restore in-app purchases, how to check user&amp;#x27;s entitlements and more.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Configuring products&lt;/h3&gt;&lt;p&gt;The first step of offering in-app purchases in an iOS app is to create products we&amp;#x27;d like to offer.&lt;/p&gt;&lt;p&gt;We can do that through App Store Connect or through a local StoreKit configuration file for testing. To get up and running quickly, we can just use a local StoreKit configuration file for now.&lt;/p&gt;&lt;img src=&quot;/images/20231009-storekit-configuration-file-add.png&quot; alt=&quot;Locally configure in-app purchase options.&quot;/&gt;&lt;div class=&quot;imageCapture&quot;&gt;StoreKit configuration file.&lt;/div&gt;&lt;p&gt;Checkout this article on &lt;a target=&quot;_blank&quot; href=&quot;/20230904-storekit-testing-in-xcode/&quot;&gt;StoreKit testing in Xcode&lt;/a&gt; to get more details on how to setup a StoreKit configuration file.&lt;/p&gt;&lt;h3&gt;Requesting products&lt;/h3&gt;&lt;p&gt;After setting up the file, we can load the configured products to show them to the user.&lt;/p&gt;&lt;p&gt;To load available products, StoreKit 2 provides the following method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let productIdentifiers = [&amp;quot;com.example.budget&amp;quot;, &amp;quot;com.example.standard&amp;quot;, &amp;quot; com.example.solidary&amp;quot;]
let products = try await Product.products(for: productIdentifiers)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each &lt;code&gt;Product&lt;/code&gt; has properties like &lt;code&gt;displayName&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt; or &lt;code&gt;displayPrice&lt;/code&gt; and more that we can directly use to show to the user.&lt;/p&gt;&lt;p&gt;If the product is an auto-renewable subscription, we can use the &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;renewalInfo&lt;/code&gt; on the &lt;code&gt;subscription&lt;/code&gt; property to provide users with appropriate informations about their subscription status.&lt;/p&gt;&lt;h3&gt;Purchasing a product&lt;/h3&gt;&lt;p&gt;If a user decides to purchase one of our products, we can call the &lt;code&gt;purchase&lt;/code&gt; method on the selected product:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let result = try await product.purchase()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This method brings up a system confirmation sheet. The user can then confirm the purchase or cancel it.&lt;/p&gt;&lt;p&gt;The value of the purchase result represents the state of the purchase:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;switch result {
case .success(let result):
    switch verificationResult {
    case .verified(let transaction):
        // Give the user access to purchased content.
        // Complete the transaction with transaction.finish()
    case .unverified(let transaction, let verificationError):
        // The purchase failed verification for the provided reason.
    }
case .pending:
    // The purchase is waiting for some user action.
    // Purchase may succeed in the future,
    // the resulting Transaction will be delivered via Transaction.updates
case .userCancelled:
    // The user cancelled the purchase.
default:
    break
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Transactions &amp;amp; current entitlements&lt;/h3&gt;&lt;p&gt;When offering paid content or features, we need to know which ones were already bought by a user to unlock them.&lt;/p&gt;&lt;p&gt;For that, StoreKit 2 provides access to current entitlements which lists all products the user is entitled to:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func refreshPurchasedProducts() async {
    for await result in Transaction.currentEntitlements {
        guard case .verified(let transaction) = result else { continue }
        if transaction.revocationDate == nil {
            self.purchasedProductIDs.insert(transaction.productID)
        } else {
            self.purchasedProductIDs.remove(transaction.productID)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Restoring in-app purchases&lt;/h3&gt;&lt;p&gt;When users reinstall an iOS app or download it on a new device, we as developers need to make sure that all in-app purchases the user previously made are restored.&lt;/p&gt;&lt;p&gt;When it comes to restoring in-app purchases, Apple recommends doing it proactively, without the user taking any actions. This can be done by using the current entitlements described above.&lt;/p&gt;&lt;p&gt;Additionally, we can offer a restore in-app purchases button which calls &lt;code&gt;AppStore.sync()&lt;/code&gt; to enforce the synchronisation of transaction information.&lt;/p&gt;&lt;p&gt;Check out this article to learn more on &lt;a target=&quot;_blank&quot; href=&quot;/20231009-restore-in-app-purchases-storekit/&quot;&gt;how to restore in-app purchases with StoreKit 2 &lt;/a&gt;.&lt;/p&gt;</content:encoded><category>storekit</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Introduction to Kotlin for Swift developers - classes, structs & enums]]></title><description><![CDATA[Learn how classes, structs and enums work in Kotlin from a Swift developer's point of view.]]></description><link>https://tanaschita.com/kotlin-for-swift-developers-part-2</link><guid isPermaLink="false">https://tanaschita.com/kotlin-for-swift-developers-part-2</guid><pubDate>Sun, 24 Sep 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are a iOS developer learning Kotlin - which is the main programming language for Android development - or you just want to explore another modern programming language, this article series may be for you.&lt;/p&gt;&lt;p&gt;In the &lt;a target=&quot;_blank&quot; href=&quot;/kotlin-for-swift-developers-part-1/&quot;&gt;first part of Kotlin for Swift developers&lt;/a&gt;, we looked at Kotlin variables, functions &amp;amp; control flow and how those are similar or different from Swift.&lt;/p&gt;&lt;p&gt;In this part, we&amp;#x27;ll do the same for classes, structs and enums.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Classes&lt;/h3&gt;&lt;p&gt;Classes in Kotlin and Swift share several similarities. Both represent reference types, support inheritance, interfaces and protocols, allow setting up initial values and behavior using constructors or initializers and more.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at an example of a Kotlin class:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class User(
    val name: String,
    val age: Int,
) {

    val allowAccess: Int
        get() = !this.isVerified

    private var isVerified: Bool = false

    init {
        // Constructor parameters name and age are available here
    }
}

val user = User(&amp;quot;tanaschita&amp;quot;, 34)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To a Swift developer, the constructor in Kotlin may seem a bit unusual. While in Swift, init parameters are defined as part of the init function, a Kotlin class constructor is defined as a part of the class header.&lt;/p&gt;&lt;p&gt;If some additional initialization code is needed, it can be defined in the &lt;code&gt;init&lt;/code&gt; block where all constructor parameters are available.&lt;/p&gt;&lt;p&gt;The same code in Swift looks as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class User {
    private let name: String
    private let age: Int
    private var isVerified: Bool = false

    var allowAccess: Int {
        return !isVerified
    }

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

let user = User(name: &amp;quot;tanaschita&amp;quot;, age: 34)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Structs&lt;/h3&gt;&lt;p&gt;Additionally to classes, Swift offers structs for value types. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;struct Circle {
    let radius: Int
}

let circle = Circle(radius: 5)
let anotherCircle = circle
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since structs are value types, &lt;code&gt;anotherCircle&lt;/code&gt; is a copy of &lt;code&gt;circle&lt;/code&gt; and not a reference to &lt;code&gt;circle&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Kotlin&amp;#x27;s value types are not as integral to the language as Swift&amp;#x27;s value types. To get similar behaviour, Kotlin provides data classes, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;data class Circle(val radius: Int)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When defining a data class, Kotlin automatically generates a &lt;code&gt;copy()&lt;/code&gt; function, which can be used to get similar behaviour as Swift&amp;#x27;s struct:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;val circle = Circle(5)
val anotherCircle = circle.copy()
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Enums&lt;/h3&gt;&lt;p&gt;Kotlin and Swift both support enumerations, which are used to represent a set of related values. A simple enumaration in Kotlin is very similar to Swift:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;enum class Weekday {
    MONDAY, TUESDAY, WEDNESDAY ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Weekday {
    case monday, tuesday, wednesday ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can provide a constructor for Kotlin enums, which is similar to rawValues in Swift enums:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;enum class Weekday(val short: String) {
    MONDAY(&amp;quot;mo&amp;quot;),
    TUESDAY(&amp;quot;tue&amp;quot;),
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Weekday: String {
    case monday = &amp;quot;mo&amp;quot;
    case tuesday = &amp;quot;tue&amp;quot;
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A powerful feature of enums in Swift is that we can associate each state with a value, even with different types. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Result {
    case success(String)
    case error(Int)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can&amp;#x27;t do that with Kotlin enums. To achieve similar behaviour, we can use Kotlin&amp;#x27;s sealed classes.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;sealed class Result {
    data class Success(val data: String) : Result()
    data class Error(val errorCode: Int) : Result()
}

fun handleResult(result: Result) {
    when (result) {
        is Result.Success -&amp;gt; {
            println(&amp;quot;Success: ${result.data}&amp;quot;)
        }
        is Result.Error -&amp;gt; {
            println(&amp;quot;Error: ${result.errorCode}&amp;quot;)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>beyond ios</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to intercept and edit a server response with Proxyman for iOS]]></title><description><![CDATA[Learn how to change response data to test different server response scenarios.]]></description><link>https://tanaschita.com/20230918-proxyman-how-to-change-request</link><guid isPermaLink="false">https://tanaschita.com/20230918-proxyman-how-to-change-request</guid><pubDate>Sun, 17 Sep 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When developing iOS applications, sending and receiving network requests is an essential part of almost every application. During the development or testing process, we often need to test different scenarios on how our app behaves with different server responses.&lt;/p&gt;&lt;p&gt;To be able to do that, we can use Proxyman - which is a proxy server app that enables us to view, inspect and change networking traffic coming in and out from our iOS application.&lt;/p&gt;&lt;p&gt;In this article, we are going to look at how to intercept and change a server response before receiving it on an iOS device or simulator with Proxyman&amp;#x27;s breakpoint tool. If you are new to Proxyman, check out this guide to &lt;a target=&quot;_blank&quot; href=&quot;/20220704-quick-guide-on-proxyman/&quot;&gt;get started with Proxyman for iOS&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s say, we are developing an iOS application which shows quotes depending on a tag which a user selected. For that, the application sends the following request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;https://api.quotable.io/quotes?tags=technology
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With an answer looking as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;count&amp;quot;: 20,
  &amp;quot;totalCount&amp;quot;: 50,
  &amp;quot;page&amp;quot;: 1,
  &amp;quot;totalPages&amp;quot;: 3,
  &amp;quot;lastItemIndex&amp;quot;: 20,
  &amp;quot;results&amp;quot;: [
    {
      &amp;quot;id&amp;quot;: &amp;quot;KMTJ0Ya3e9&amp;quot;,
      &amp;quot;author&amp;quot;: &amp;quot;Thomas Edison&amp;quot;,
      &amp;quot;content&amp;quot;: &amp;quot;To invent, you need a good imagination and a pile of junk.&amp;quot;,
      &amp;quot;tags&amp;quot;: [&amp;quot;Creativity&amp;quot;, &amp;quot;Science&amp;quot;, &amp;quot;Technology&amp;quot;]
    },
    {
      &amp;quot;id&amp;quot;: &amp;quot;sXhSm0WMZqL&amp;quot;,
      &amp;quot;author&amp;quot;: &amp;quot;Alan Watts&amp;quot;,
      &amp;quot;content&amp;quot;: &amp;quot;Technology is destructive only in the hands of people who do not realize that they are one and the same process as the universe.&amp;quot;,
      &amp;quot;tags&amp;quot;: [&amp;quot;Technology&amp;quot;]
    },
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After receiving the response, we show the results in a list.&lt;/p&gt;&lt;p&gt;We also implemented an no results view to show to the user when no quotes are available for a certain tag, so we&amp;#x27;d like to test that by changing the response to an empty list.&lt;/p&gt;&lt;p&gt;To change the response to be able to test that scenario, we can follow these steps:&lt;/p&gt;&lt;h4&gt;1. Creating a breakpoint&lt;/h4&gt;&lt;p&gt;One way to create a breakpoint is to right-click on the request and select &lt;code&gt;tools &amp;gt; breakpoint&lt;/code&gt;:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/068e75db7c94bd6b2ad591512c47e7a2/1d10b/20231002-proxyman-create-breakpoint.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:54.666666666666664%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACCUlEQVQoz2WS3W6cMBSE9zGaZAHbGAPmx4BhF3bzs0kjJVIr9bJ3ff+3+Cq72VZRL0bWsY7nzMzxrukcShsymZPlOa1zuGGi6Xpq2yGURuYFUgVo1odnTFmjizL2jX6OvW6c6IeR3dPlmYfHC/O8IJqW0m/47YXl/MpyulD3M9K06MqRFR3Ht58U7UxmHKqeaP09lTtSuZVxfWbX9gNJJkmFQmpDUdaUtkGXFYfjxuXlFdt2NG1HWVmK0qJNFRF6g9riA6aygdBFS6nMKUzF4Dz9jyPN+8h6PLNsZ0prI2FtW4TU6KJCaxMjCG9zXaByjVQ5u9t9yj4V3CVZnFo1jsRY8rJk8gvb6Z4wtLItSilub79gtKC2FlW2pCJHCMl+n3Bze8cu2A0IhEG2nw8opbnbZ4yT53S+j3d97zDuATm8k7RfMZ3H31+orCXLUtI0jecnwqBwmg/RRlDulwNPzy+RcBgGlsdvbG+/yP13TDvRLyu1bZBCRIVJkvwjDLbD1wkqQx0Il8ORdTvF/NrOsW0rT49napOjpKByE13TMNYGkytEln0mVLrAL0cq23Bzl0TLx3Vj8nNUshxWDusJXRiklPHfTX1HoSS5/IP/FJZ1Ey2HOpymqj9UC4bRcwkReI+UClvkLJ2NlkOGfy2Hx9ccA671dftX5EUZNx42LITAjjPWjdHu1fJv4v9FBesx8I0AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Creating a breakpoint with Proxyman.&quot; title=&quot;Creating a breakpoint with Proxyman.&quot; src=&quot;/static/068e75db7c94bd6b2ad591512c47e7a2/2e9ed/20231002-proxyman-create-breakpoint.png&quot; srcSet=&quot;/static/068e75db7c94bd6b2ad591512c47e7a2/12d18/20231002-proxyman-create-breakpoint.png 375w,/static/068e75db7c94bd6b2ad591512c47e7a2/44507/20231002-proxyman-create-breakpoint.png 750w,/static/068e75db7c94bd6b2ad591512c47e7a2/2e9ed/20231002-proxyman-create-breakpoint.png 1500w,/static/068e75db7c94bd6b2ad591512c47e7a2/1d10b/20231002-proxyman-create-breakpoint.png 2128w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Creating a breakpoint with Proxyman.&lt;/div&gt;&lt;p&gt;After doing that, Proxyman opens a settings dialog where we can configure whether we&amp;#x27;d like to intercept the request, the response or both and define matching rules for the url to match.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/176c5d9afc6c63d7910eec2460f2137a/1bad6/20231002-proxyman-breakpoint-settings.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:28.53333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJElEQVQY013QXU7CUBCG4e5De3r6/19LW9BakErBaAWiBqPx0jW4Bdf9mlMBgxdPMjMXM19GsyyLJEnILnKqasxkMmFUFNiOi+M6eJ6He8I/8vxTjuuilWU5LGhWa7rNjvn6jcXmlbZ/YtbvyKdLwqohHs+IqulQR3tBUePvqdrLr9CklAghuJm3dMsll+2W7q7n5XlLt/4gTCvCMCSMYvwgHCRpSpykmJaFNE2kaR1pWZahFEXBtLmmajraxYqyGBEEPqaUGIaBlIocqF6FEELHEAJDP0foZxhCR3McByWKouGXUZqTpNnQB0FApJL5/pDyMFNLbdse/mvZNqYXYwcpphui/V4Sx6uG8dcf0vyn5nVd0z/cM75qcB+/yN6/MW8/+QFz97YBbaw1zAAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Breakpoint settings.&quot; title=&quot;Breakpoint settings.&quot; src=&quot;/static/176c5d9afc6c63d7910eec2460f2137a/2e9ed/20231002-proxyman-breakpoint-settings.png&quot; srcSet=&quot;/static/176c5d9afc6c63d7910eec2460f2137a/12d18/20231002-proxyman-breakpoint-settings.png 375w,/static/176c5d9afc6c63d7910eec2460f2137a/44507/20231002-proxyman-breakpoint-settings.png 750w,/static/176c5d9afc6c63d7910eec2460f2137a/2e9ed/20231002-proxyman-breakpoint-settings.png 1500w,/static/176c5d9afc6c63d7910eec2460f2137a/1bad6/20231002-proxyman-breakpoint-settings.png 1570w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Breakpoint settings.&lt;/div&gt;&lt;p&gt;In our example, we use the wildcard &lt;code&gt;*&lt;/code&gt; for the tags query value to intercept the response for any tag. Since we only want to edit the response, we leave the request option unchecked.&lt;/p&gt;&lt;h4&gt;2. Intercepting and modifying the response&lt;/h4&gt;&lt;p&gt;After configuring the breakpoint, we can now send a request we&amp;#x27;d like to intercept. After doing that, Proxyman will open the following window when receiving a response:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1c848bddbebc052a7bc6c26457af3959/8c3ac/20231002-proxyman-intercept-change.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:64.26666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAABr0lEQVQ4y52R63LaMBCF/RKlAXS1LHCRbAvJDhhIIHT6/o90OlouM7RJk+mPb6SVVkdnd4uf5x2GzQb98Hwnph7P2xHbcY+Uery9nfF6PGFDZztstyPG3R7juMN+f6B93w/0rojdGn1KiCkh5TVGOOfQhYiYBnjvEULAOkaEsKacsF4TXddRnFfvG8otlFZQ6hGt9WUv5V938o+zW6yv74qy1Ch1CVtZWFvBWvtfVNbClBpFrr3tOwijIKQGv/4qpQLnghBCgIu8ynfhQpI7HwYUoRuw/NXDDDWYXGJWGihjwLkC4zlZYfLEMZ0xTL5P8W3y9C75jssSRb3y4NxASgO7qNG0AVJXhFIatWtQN4FcXxzzu/PHmIMxhiJP0iyWmDEBY5cIMUEoDSY0JbiQkA4nGFNCcE7lf0T+tMiuuNSYc0mCx9MZ290Bvg3Um8skL30V4t+CmWLlWxK6OXw5ntCGiGpRg91L4g+lfQQJNl2AXdbkUJUVfriG9hma8DXxK2IkOJ1zcncTYUJRCwhK+rzMTO43DcX5FnnSK99gUa8u4nc45ox9zpxh5Ryc8/gNAdtwde9I9VcAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Intercept and change a response.&quot; title=&quot;Intercept and change a response.&quot; src=&quot;/static/1c848bddbebc052a7bc6c26457af3959/2e9ed/20231002-proxyman-intercept-change.png&quot; srcSet=&quot;/static/1c848bddbebc052a7bc6c26457af3959/12d18/20231002-proxyman-intercept-change.png 375w,/static/1c848bddbebc052a7bc6c26457af3959/44507/20231002-proxyman-intercept-change.png 750w,/static/1c848bddbebc052a7bc6c26457af3959/2e9ed/20231002-proxyman-intercept-change.png 1500w,/static/1c848bddbebc052a7bc6c26457af3959/e90d0/20231002-proxyman-intercept-change.png 2250w,/static/1c848bddbebc052a7bc6c26457af3959/8c3ac/20231002-proxyman-intercept-change.png 2586w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Intercept and change a response.&lt;/div&gt;&lt;p&gt;In this window, we can edit the response the way we want, for example to an empty results array - as shown above. To continue, we can use the execute button in the bottom right corner.&lt;/p&gt;&lt;p&gt;With this simple setup, we can test many different scenario concerning the server communication. The Breakpoint tool is not only suitable to modify the response&amp;#x27;s body. We can also use it to modify headers or status code, for example to test different server errors.&lt;/p&gt;</content:encoded><category>networking</category><category>tools</category><category>ios</category></item><item><title><![CDATA[How to manually subscribe to changes of SwiftUI's @Observable objects]]></title><description><![CDATA[Learn how to use the withObservationTracking method of the Observation framework.]]></description><link>https://tanaschita.com/20230822-observation-framework-subscribe-to-changes</link><guid isPermaLink="false">https://tanaschita.com/20230822-observation-framework-subscribe-to-changes</guid><pubDate>Mon, 21 Aug 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At WWDC23, Apple released a new Observation framework for SwiftUI. We already covered the basics in this &lt;a target=&quot;_blank&quot; href=&quot;/20230807-migrating-to-observation/&quot;&gt;article on how to migrate from ObservableObject to the Observation macro in SwiftUI&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll dive a bit deeper and look at how we can subscribe to changes of &lt;code&gt;@Observable&lt;/code&gt; objects manually. Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;When working with &lt;code&gt;@Observable&lt;/code&gt; marked objects within SwiftUI views, SwiftUI automatically takes care of tracking changes und updating views when needed. But there are still use cases, where we need to manually subscribe to those changes, for example to trigger actions such as dismissing a view or to trigger view unrelated actions such as persisting data o.s.&lt;/p&gt;&lt;p&gt;When working with &lt;code&gt;@Published&lt;/code&gt; properties, we have the opportunity to access the Combine publisher for a property by prefixing it with an &lt;code&gt;$&lt;/code&gt; to suscribe to property changes, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;class UserViewModel: ObservableObject {
    @Published var name = &amp;quot;&amp;quot;

    func subscribeToChanges() {
        cancellable = viewModel.$name.sink { [weak self] newValue in
            print(newValue)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the Observation framework, we can&amp;#x27;t do that anymore. For tracking changes outside of SwiftUI, it provides the &lt;code&gt;withObservationTracking(_:onChange:)&lt;/code&gt; method.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at an example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@Observable
class UserViewModel {

    var name: String = &amp;quot;&amp;quot;

    func subscribeToChanges() {
        withObservationTracking {
            _ = name
        } onChange: { [weak self] in
            guard let self else { return }
            print(self.name)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the functions takes two closures as its parameters: &lt;code&gt;apply&lt;/code&gt; and &lt;code&gt;onChange&lt;/code&gt;. The &lt;code&gt;apply&lt;/code&gt; closure contains which properties to track and the &lt;code&gt;onChange&lt;/code&gt; closure is invoked when the value of a property changes.&lt;/p&gt;&lt;p&gt;There are two important differences to the mentioned Combine publisher approach though:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The &lt;code&gt;onChange&lt;/code&gt; closure is called right before the new values are set, so we don&amp;#x27;t have access to the new values in the &lt;code&gt;onChange&lt;/code&gt; closure&lt;/li&gt;&lt;li&gt;The &lt;code&gt;onChange&lt;/code&gt; method is only called once - when one of the tracked values changes - and is never called again.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Unfortunately, the Observation framework does currently not provide an API to be able to continuously subscribe to changes and getting their new values. Since the Observation framework is currently in beta, the framework will hopefully provide this functionality in the future.&lt;/p&gt;</content:encoded><category>observation</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Introduction to Kotlin for Swift developers - variables, functions & control flow]]></title><description><![CDATA[Learn the main concepts of Kotlin from a Swift developer's point of view.]]></description><link>https://tanaschita.com/kotlin-for-swift-developers-part-1</link><guid isPermaLink="false">https://tanaschita.com/kotlin-for-swift-developers-part-1</guid><pubDate>Mon, 14 Aug 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you are a iOS developer learning Kotlin - which is the main programming language for Android development - or you just want to take a glimpse into another modern programming language, this article series may be for you.&lt;/p&gt;&lt;p&gt;This is a 4-part article series which will cover the following Kotlin topics from a Swift developer&amp;#x27;s point of view:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;In part 1 (this article), we&amp;#x27;ll look into basic concepts like variables, functions and control flow.&lt;/li&gt;&lt;li&gt;In &lt;a target=&quot;_blank&quot; href=&quot;/kotlin-for-swift-developers-part-2/&quot;&gt;part 2&lt;/a&gt;, we&amp;#x27;ll take a closer look into classes, structs and enums.&lt;/li&gt;&lt;li&gt;In &lt;a target=&quot;_blank&quot; href=&quot;/kotlin-for-swift-developers-part-3/&quot;&gt;part 3&lt;/a&gt;, we&amp;#x27;ll look into protocols, extensions and generics.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Variables &amp;amp; Optionals&lt;/h3&gt;&lt;p&gt;The syntax between Swift and Kotlin is quite similar. As a Swift developer, you most likely will be able to read Kotlin code and understand most of it right from the beginning, and also recognize a lot of features you know from Swift.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at the following code:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;fun sumOf(x: Int, y: Int? = null): Int {
    println(&amp;quot;x is $x, y is $y&amp;quot;)

    if (y != null) {
        val result = x + y
        return result
    }
    return x
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func sumOf(x: Int, y: Int? = nil) -&amp;gt; Int {
    print(&amp;quot;x is \(x), y is \(y)&amp;quot;)

    if let y {
        let result = x + y
        return result
    }
    return x
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the Kotlin code is almost self-explanatory since the syntax in both languages is only slightly different. A few things to point out:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;To define constants, Kotlin provides the &lt;code&gt;val&lt;/code&gt; keyword (equivalent to &lt;code&gt;let&lt;/code&gt; in Swift).&lt;/li&gt;&lt;li&gt;For mutable variables, it even has the same keyword as Swift: &lt;code&gt;var result = ...&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Just like Swift, Kotlin supports type inference. The type of the &lt;code&gt;result&lt;/code&gt; variable is inferred, we don&amp;#x27;t need to explicitly specify it with &lt;code&gt;val result: Int = x + y&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;Just like Swift, Kotlin supports optionals. While we use a &lt;code&gt;if let&lt;/code&gt; binding in Swift to unwrap optionals, in Kotlin, an optional variable is automatically casted to non-nullable after a &lt;code&gt;null&lt;/code&gt; check.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Similar to Swift, Kotlin also supports optional chaining and forced unwrapping:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;result?.user?.isValidated // optional chaining in Kotlin
result!!.user!!.isValidated // forced unwrapping in Kotlin
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Functions&lt;/h3&gt;&lt;p&gt;In the example above, we already saw a function in action:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;fun sumOf(x: Int, y: Int? = null): Int {
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similar to Swift, functions in Kotlin support default arguments. Kotlin provides a lot of flexibility for calling functions. We can call the function without the argument names or by using named arguments. When using named arguments, we can freely change the order that they are listed in.&lt;/p&gt;&lt;p&gt;Here are valid possibilities we can call the above function in Kotlin:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;sumOf(1)
sumOf(x = 1)
sumOf(1, 4)
sumOf(y = 4, x = 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similar to Swift, Kotlin functions are first-class which means they can be stored in variables and can be passed as arguments to and returned from other functions. A function type in Kotlin is defined as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;(Int) -&amp;gt; String
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which is the same syntax as in Swift. Kotlin also provides similar higher-order functions like map, filter, reduce and more:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;val numbers = intArrayOf(1, 2, 3)
val numbersTimesTwo = numbers.map { it * 2 }
val sum: Int = numbers.reduce { acc, next -&amp;gt; acc + next }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The equivalent in Swift looks as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let numbers = [1, 2, 3]
let numbersTimesTwo = numbers.map { $0 * 2 }
let sum = numbers.reduce(0) { $0 + $1 }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In Kotlin, the implicit parameter name &lt;code&gt;it&lt;/code&gt; can only be used for single parameters.&lt;/p&gt;&lt;h3&gt;Control flow&lt;/h3&gt;&lt;p&gt;Kotlin provides similar control flow statements we already know from Swift. An if-else-statement was already shown in previous examples. Let&amp;#x27;s look at two other often used statements - the when statement and the for-loop.&lt;/p&gt;&lt;p&gt;&lt;code&gt;when&lt;/code&gt; defines a conditional expression with multiple branches.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;enum class Color {
  RED, GREEN, BLUE, BROWN
}

when (color) {
    Color.RED -&amp;gt; println(&amp;quot;red&amp;quot;)
    Color.GREEN, Color.BLUE -&amp;gt; println(&amp;quot;green or blue&amp;quot;)
    else -&amp;gt; println(&amp;quot;otherwise&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, it is very similar to the switch statement in Swift. Just like in Swift, the &lt;code&gt;else&lt;/code&gt; statement is not required when all possible cases are covered. The Swift equivalent looks as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;enum Color {
    case red, green, blue, brown
}

switch self {
case .red:
    print(&amp;quot;red&amp;quot;)
case .green, .blue:
    print(&amp;quot;green or blue&amp;quot;)
default:
    print(&amp;quot;otherwise&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For loops in Kotlin are also very similar to those in Swift:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Kotlin&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;for (item in items) {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only difference are the parentheses which we can leave out in Swift:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;for item in items {
    // ...
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>beyond ios</category><category>swift</category><category>ios</category></item><item><title><![CDATA[The difference between @StateObject and @ObservedObject in SwiftUI]]></title><description><![CDATA[Learn how to correctly use the property wrappers StateObject and ObservedObject.]]></description><link>https://tanaschita.com/20230731-stateobject-observedobject-swiftui</link><guid isPermaLink="false">https://tanaschita.com/20230731-stateobject-observedobject-swiftui</guid><pubDate>Sun, 30 Jul 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When it comes to state management in SwiftUI combined with reference types, SwiftUI provides two property wrappers: &lt;code&gt;@StateObject&lt;/code&gt; and &lt;code&gt;@ObservedObject&lt;/code&gt;. Understanding the difference between them is crucial for building robust SwiftUI applications, as it determines how data is managed and flows through the view hierarchy.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: Apple introduced the Observation framework at WWDC23 which will make these two property wrappers obsolete in the future. The knowledge around them is still useful since they are used in many existing applications. The knowledge might also help migrate to new Observation framework.&lt;/p&gt;&lt;p&gt;So let&amp;#x27;s directly dive in and look at an example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s look at the following example of a view and a view model working together:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct RandomNameGeneratorView: View {
    @ObservedObject private var viewModel = RandomNameGeneratorViewModel()

    var body: some View {
        VStack {
            Text(viewModel.generatedName ?? &amp;quot;Tap the button to generate a new name&amp;quot;)
            Button(&amp;quot;Generate&amp;quot;) {
                viewModel.generateNewName()
            }
        }
    }
}

class RandomNameGeneratorViewModel: ObservableObject {
    @Published var generatedName: String?

    func generateNewName() {
        generatedName = ...
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example above shows a view which the user can use to generate a new name every time they tap the generate button. The view references the view model with the &lt;code&gt;@ObservedObject&lt;/code&gt; property wrapper so the view is automatically updated when the view model&amp;#x27;s published properties change.&lt;/p&gt;&lt;p&gt;When using the view, for example as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    var body: some View {
        RandomNameGeneratorView()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;everything seems to work fine.&lt;/p&gt;&lt;p&gt;But there is actually a potential error we introduced. As soon as our view hierarchy becomes more complex, we might run into the problem, that a generated name suddenly becomes empty without us resetting it explicitly. The reason behind this behaviour is that we used &lt;code&gt;@ObservedObject&lt;/code&gt; to subscribe to model changes.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;An &lt;code&gt;@ObservedObject&lt;/code&gt; is reinitialized every time their containing view redraws.&lt;/strong&gt; Since SwiftUI might recreate a view at any time, using &lt;code&gt;@ObservedObject&lt;/code&gt; in our example might lead to unwanted behaviour and some headache trying to find the cause.&lt;/p&gt;&lt;p&gt;The correct approach in this case is to use the &lt;code&gt;@StateObject&lt;/code&gt; property wrapper instead. &lt;code&gt;@StateObject&lt;/code&gt; &lt;strong&gt;makes sure the view model retains state between view redraws&lt;/strong&gt; so we don&amp;#x27;t lose any state.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;An &lt;code&gt;@ObservedObject&lt;/code&gt; should only be used to pass a &lt;code&gt;@StateObject&lt;/code&gt; into a subview&lt;/strong&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView: View {
    @StateObject private var viewModel = RandomNameGeneratorViewModel()

    var body: some View {
        RandomNameGeneratorView(viewModel: viewModel)
    }
}

struct RandomNameGeneratorView: View {
    @ObservedObject var viewModel: RandomNameGeneratorViewModel

    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we create a two-way binding by passing in a &lt;code&gt;@StateObject&lt;/code&gt; and referencing it as an &lt;code&gt;@ObservedObject&lt;/code&gt;. This way, every change to published properties of the view model made by any of the view will also be reflected in the other view.&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to automate taking screenshots with fastlane for iOS]]></title><description><![CDATA[Learn how to setup fastlane's snapshot action.]]></description><link>https://tanaschita.com/20230724-fastlane-screenshots-ios</link><guid isPermaLink="false">https://tanaschita.com/20230724-fastlane-screenshots-ios</guid><pubDate>Sun, 23 Jul 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As mentioned in this &lt;a target=&quot;_blank&quot; href=&quot;/20230213-fastlane-ios-developer-guide/&quot;&gt;fastlane guide for iOS&lt;/a&gt;, fastlane provides a &lt;code&gt;snapshot&lt;/code&gt; action to automatically generate screenshots for an iOS application.&lt;/p&gt;&lt;p&gt;Automatically taking screenshots of a mobile application can serve us developers in different ways, for example as part of publishing the app to the App Store, as part of a QA process or as part of UI testing to identify changes to previous versions.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how we can use fastlane&amp;#x27;s snapshot action to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The &lt;code&gt;snapshot&lt;/code&gt; action uses the capabilities of UI tests to create the desired application state. If you never worked with UI tests before, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/testing-ui-swiftui-xctest-framework/&quot;&gt;getting started with UI tests&lt;/a&gt; to learn more.&lt;/p&gt;&lt;p&gt;To setup snapshot, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Creating a new UI test target&lt;/li&gt;&lt;li&gt;Running &lt;code&gt;fastlane snapshot init&lt;/code&gt; in the project folder which will create a &lt;code&gt;Snapfile&lt;/code&gt; and &lt;code&gt;./SnapshotHelper.swift&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Open the created &lt;code&gt;Snapfile&lt;/code&gt; file to configure languages, devices and more, for example:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;devices([
 &amp;quot;iPhone 14&amp;quot;
 ])

output_directory(&amp;quot;./screenshots&amp;quot;)
clear_previous_screenshots(true)
override_status_bar(true)

&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;4&quot;&gt;&lt;li&gt;Add the created &lt;code&gt;./SnapshotHelper.swift&lt;/code&gt; file to the UI Test target&lt;/li&gt;&lt;li&gt;Add a new Xcode scheme for the created UI Test target, open its &lt;code&gt;Build&lt;/code&gt; section and enable the &lt;code&gt;Run&lt;/code&gt; and &lt;code&gt;Shared&lt;/code&gt; checkbox.&lt;/li&gt;&lt;li&gt;Add the following code in the &lt;code&gt;setUp()&lt;/code&gt; method:&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let app = XCUIApplication()
setupSnapshot(app)
app.launch()
&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;7&quot;&gt;&lt;li&gt;Add interactions to the UI test class that lead to desired screens and call &lt;code&gt;snapshot(&amp;quot;NameOfTheScreen&amp;quot;)&lt;/code&gt; to take a screenshot where needed.&lt;/li&gt;&lt;li&gt;Run &lt;code&gt;fastlane snapshot&lt;/code&gt; in the terminal. It will create the screenshots inside the &lt;code&gt;fastlane/screenshots&lt;/code&gt; directory.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;To add device frames around created screenshots, we can use the &lt;code&gt;fastlane frameit&lt;/code&gt; action. We also have the possibility to add a background and titles by setting up a &lt;code&gt;Framefile.json&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>continuous integration</category><category>testing</category><category>ios</category></item><item><title><![CDATA[Beginner's guide on Flutter for iOS developers]]></title><description><![CDATA[Learn the main concepts of Flutter from a native iOS developer's point of view.]]></description><link>https://tanaschita.com/20230717-flutter-for-ios-developers</link><guid isPermaLink="false">https://tanaschita.com/20230717-flutter-for-ios-developers</guid><pubDate>Sun, 16 Jul 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I was experimenting with Flutter for a small project of mine. Flutter is an open source framework by Google for building cross-platform applications from a single codebase.&lt;/p&gt;&lt;p&gt;Knowledge in Swift and SwiftUI are valuable when building apps with Flutter. With this article, I would like to give you a overview of the main concepts of Flutter from a native iOS developer&amp;#x27;s point of view.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s get started.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Dart as programming language&lt;/h3&gt;&lt;p&gt;Dart is the programming language used by Flutter developers.&lt;/p&gt;&lt;p&gt;For a Swift developer, learning Dart feels familiar. Both languages share concepts like type safety, type inference, optionals, generics, concurrency with async/await, error handling, extensions and more.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at the following Dart example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;API api = API(); // 1.

try {
  User user = await api.login(username, password);
} catch (error) {
  // Handle error.
}


class User {
  String _username; // 2.

  User(this._username); // 3.
}

class API {

  User? _currentUser;

  Future&amp;lt;User&amp;gt; login(String username, String password) async {

      // 4.
      if (_currentUser != null) {
        throw UserAlreadyLoggedIn(_currentUser);
      }
      // Network request
      return user;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To a Swift developer, the code above should be almost self-explanatory. Some things to point out here are:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Variables defined without any keyword are similar to a &lt;code&gt;var&lt;/code&gt; in Swift. To define a constant variable in Dart, we can use the &lt;code&gt;final&lt;/code&gt; keyword which is similar to the &lt;code&gt;let&lt;/code&gt; keyword in Swift.&lt;/li&gt;&lt;li&gt;Underscores in Dart are used to make variables or classes private.&lt;/li&gt;&lt;li&gt;Since writing constructors to assign all class fields is often redundant, Dart offers syntactic sugar that looks like shown in the example above.&lt;/li&gt;&lt;li&gt;Optionals in Dart are defined and handled just like in Swift with &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;!&lt;/code&gt;. To unwrap an optional safely, Dart offers the if statement as shown in the example above. Inside the if-statement, &lt;code&gt;_currentUser&lt;/code&gt; is treated as a non-optional.&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Widgets as UI elements&lt;/h3&gt;&lt;p&gt;Just like SwiftUI, Flutter provides a declarative API to build user interfaces. While SwiftUI represents UI elements as views, Flutter does the same with so called widgets. To compose layouts, both SwiftUI and Flutter nest UI components within one another.&lt;/p&gt;&lt;p&gt;For example, the following SwiftUI code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Text(&amp;quot;Hello world!&amp;quot;)
  .padding(5)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;looks as follows in Flutter:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;Padding(
  padding: EdgeInsets.all(5.0),
  child: Text(&amp;quot;Hello world!&amp;quot;),
)));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, while SwiftUI uses view modifiers to configure views, Flutter uses widget properties.&lt;/p&gt;&lt;p&gt;In SwiftUI, views may expand to the available space or limit their size to their content. Flutter widgets behave similar.&lt;/p&gt;&lt;p&gt;When layouting views in SwiftUI, the parent view proposes a size to its child view and then renders it at the returned size. Flutter differs at that point because the parent component can override the child&amp;#x27;s returned desired size. The parent widget passes constraints to its child widget which includes minimum and maximum values for height and width. If the requested size doesn&amp;#x27;t fit in the constraints, the parent limits the child view to fit the constraints.&lt;/p&gt;&lt;p&gt;Just like SwiftUI, Flutter provides a widget catalog which offers widgets for aligning and positioning their children. For example, equivalents to SwiftUI&amp;#x27;s &lt;code&gt;HStack&lt;/code&gt; and &lt;code&gt;VStack&lt;/code&gt; are &lt;code&gt;Row&lt;/code&gt; and &lt;code&gt;Column&lt;/code&gt; in Flutter.&lt;/p&gt;&lt;p&gt;Aligning in SwiftUI:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack {
  Text(&amp;quot;Hello&amp;quot;),
  Text(&amp;quot;world!&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Aligning in Flutter:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: const [
    Text(&amp;#x27;Hello&amp;#x27;),
    Text(&amp;#x27;world!&amp;#x27;),
  ],
),
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Other examples of Flutter widgets are &lt;code&gt;ListView&lt;/code&gt; to create lists, &lt;code&gt;GridView&lt;/code&gt; for displaying a grid or &lt;code&gt;SingleChildScrollView&lt;/code&gt; for creating a scroll view. These are also resembling their equivalents in SwiftUI.&lt;/p&gt;&lt;h3&gt;Managing state&lt;/h3&gt;&lt;p&gt;Just like SwiftUI, Flutter follows the declarative approach when it comes to state management. Which means that every state change leads to a redraw of the user interface.&lt;/p&gt;&lt;p&gt;While SwiftUI provides property wrappers like &lt;code&gt;@State&lt;/code&gt; &amp;amp; &lt;code&gt;@Binding&lt;/code&gt; and &lt;code&gt;StateObject&lt;/code&gt; &amp;amp; &lt;code&gt;ObservedObject&lt;/code&gt;, Flutter works with so called stateful widgets which consist of a subclass of &lt;code&gt;StatefulWidget&lt;/code&gt; and &lt;code&gt;State&lt;/code&gt;, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State&amp;lt;MyStatefulWidget&amp;gt; createState() =&amp;gt; _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State&amp;lt;MyStatefulWidget&amp;gt; {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: ...,
      body: ...,
      bottomNavigationBar: BottomNavigationBar(
        items: const &amp;lt;BottomNavigationBarItem&amp;gt;[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: &amp;#x27;Home&amp;#x27;,
          ),
          ...
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the &lt;code&gt;State&lt;/code&gt; object stores the widget&amp;#x27;s state. When we call &lt;code&gt;setState()&lt;/code&gt; like in the &lt;code&gt;_onItemTapped&lt;/code&gt; function, the framework redraws the widget.&lt;/p&gt;&lt;p&gt;If a widget does not depend on anything other than the initial configuration we can use a &lt;code&gt;StatelessWidget&lt;/code&gt; instead.&lt;/p&gt;</content:encoded><category>beyond ios</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick tip on how to split text into words with the Natural Language framework in Swift.]]></title><description><![CDATA[Learn how the tokenizer of the Natural Language framework works.]]></description><link>https://tanaschita.com/20230619-tokenizing-words-with-natural-language-framework</link><guid isPermaLink="false">https://tanaschita.com/20230619-tokenizing-words-with-natural-language-framework</guid><pubDate>Sun, 18 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When it comes to splitting a text into words, the first solution that might come to mind is to simply split the string components by whitespace. But this solution doesn&amp;#x27;t work for all languages. For example, Chinese and Japanese don&amp;#x27;t use spaces to delimit words.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The Natural Language framework tokenizer provides the possibility to tokenize a string ensuring correct behaviour for all languages. For that, the framework provides the &lt;code&gt;NLTokenizer&lt;/code&gt; type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func words(for text: String) -&amp;gt; [String] {
    let tokenizer = NLTokenizer(unit: .word)
    tokenizer.string = text
    return tokenizer.tokens(for: text.startIndex..&amp;lt;text.endIndex).map { String(text[$0]) }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown above, we initialize the tokenizer with &lt;code&gt;.word&lt;/code&gt; as unit and then simply call the &lt;code&gt;tokens&lt;/code&gt; method which returns the ranges corresponding to the tokens.&lt;/p&gt;&lt;p&gt;And that&amp;#x27;s basically it. The tokenizer also accepts other units than words, for example &lt;code&gt;.sentence&lt;/code&gt; to split a text into sentences or &lt;code&gt;.paragraph&lt;/code&gt; to split it into paragraphs.&lt;/p&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to find synonyms with the Natural Language framework for iOS]]></title><description><![CDATA[Learn how to find similar words or sentences in Swift.]]></description><link>https://tanaschita.com/20230612-find-similar-words-natural-language-framework-ios</link><guid isPermaLink="false">https://tanaschita.com/20230612-find-similar-words-natural-language-framework-ios</guid><pubDate>Sun, 11 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The possibility to find synonyms for a given word can help us improve the user experience in an iOS application, for example to provide more intelligent search results or being able to offer similar content.&lt;/p&gt;&lt;p&gt;The Natural Language framework provides the synonyms functionality out of the box with its &lt;code&gt;NLEmbedding&lt;/code&gt; type which represents a vector map of strings.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;We can use its neighbors function to find synonyms as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func synonyms(for word: String, maximumCount: Int = 5) -&amp;gt; [String] {
    guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return [] }
    let synonyms = embedding.neighbors(for: word, maximumCount: maximumCount).map { $0.0 }
    return synonyms
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To support different languages, we could improve the method above by detecting the language automatically. The Natural Language framework supports this functionality out of the box. Check out this &lt;a target=&quot;_blank&quot; href=&quot;/20230501-detect-language-with-natural-language-framework/&quot;&gt;article on how to detect the language of a text with the Natural Language framework&lt;/a&gt; to learn how to do that.&lt;/p&gt;&lt;p&gt;The neighbors function not only provides synonyms but also their distance to the given word. Strings that are semantically similar have a smaller distance value. We can find out the distance between two words directly as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let distance = embedding.distance(between: word, and: anotherWord)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The distance functionality can also be used to find out similarity between sentences, which can be really usefull for example when building an FAQ service, it could suggest related questions based on the user&amp;#x27;s entered question.&lt;/p&gt;&lt;p&gt;To get the distance between two sentences, we create an embedder by using its &lt;code&gt;sentenceEmbedding&lt;/code&gt; function and then use it just like we used it with words:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;guard let embedding = NLEmbedding.sentenceEmbedding(for: .english) else { return }
let distance = embedding.distance(between: sentence, and: anotherSentence)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to map a SwiftUI state to a binding of a different type]]></title><description><![CDATA[Learn how to create custom SwiftUI bindings.]]></description><link>https://tanaschita.com/20230605-swiftui-state-binding-different-types</link><guid isPermaLink="false">https://tanaschita.com/20230605-swiftui-state-binding-different-types</guid><pubDate>Sun, 04 Jun 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with states and bindings in SwiftUI, we sometimes might have a use case where we need to map a state type to a different binding type. Let&amp;#x27;s look at an example and see how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Imagine, we have a SwiftUI view with a selection from a list and a button to save that selection. The button should be disabled as long as the user hasn&amp;#x27;t made a selection yet:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct SaveView: View {

    @State private var selectedOption: SelectionOption?

    var body: some View {
        VStack {
            SelectionView(selectedOption: $selectedOption)
            Spacer()
            SaveButton(action: {}, isDisabled: ???)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;isDisabled&lt;/code&gt; parameter is of type &lt;code&gt;Bindable&amp;lt;Bool&amp;gt;&lt;/code&gt; so we need some kind of mapping from the &lt;code&gt;selectedOption&lt;/code&gt; state property. For that case, we can initialize a &lt;code&gt;Binding&lt;/code&gt; as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;SaveButton(action: {}, isDisabled: Binding(
    get: { selectedOption == nil },
    set: { _ in }))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown above, we can create a custom binding using the &lt;code&gt;Binding&lt;/code&gt; type which takes a &lt;code&gt;get&lt;/code&gt; and a &lt;code&gt;set&lt;/code&gt; closure. This is really useful when we need to add custom logic to a binding.&lt;/p&gt;</content:encoded><category>swiftui</category><category>property wrappers</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Continuous integration and delivery for iOS]]></title><description><![CDATA[Learn CI/CD concepts and possible workflows for iOS applications.]]></description><link>https://tanaschita.com/20230529-continuous-integration-and-delivery-for-ios</link><guid isPermaLink="false">https://tanaschita.com/20230529-continuous-integration-and-delivery-for-ios</guid><pubDate>Sun, 28 May 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Continuous integration, delivery and deployment practices help us continuously and automatically build, test and deploy iterative code changes. Using CI/CD can help developer teams to improve the quality of the code base, reduce the time and effort required to release new features, and make it easier for team members to collaborate on code. It can also help to improve the reliability and stability of deployments by automating the deployment process and providing early feedback on code changes.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at CI/CD concepts and how a CI/CD workflow can be realized when developing iOS applications.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;CI/CD concepts&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Continuous integration&lt;/strong&gt; (CI) is a practice in which developers regularly merge their code changes into a central repository, where they are automatically built and tested. This allows teams to detect and fix errors quickly and ensure that their code is always in a deployable state.&lt;/p&gt;&lt;p&gt;CI typically involves setting up an automated build and test pipeline that is triggered whenever a developer pushes a code change. This pipeline can include tasks such as compiling code, running static analysis tools and running tests. If any of these tasks fail, the pipeline can be configured to alert the team so that they can investigate and fix the issue.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Continuous delivery&lt;/strong&gt; (CD) takes continuous integration a step further by also deploying the code changes to a staging or production environment.&lt;/p&gt;&lt;p&gt;CD can also refer to continuous deployment. The key difference between continuous delivery and continuous deployment is that continuous delivery allows for manual intervention and review before code is deployed to production, while continuous deployment automatically deploys all code changes to production as soon as they are ready.&lt;/p&gt;&lt;h3&gt;Main components of a CI/CD workflow&lt;/h3&gt;&lt;p&gt;The main components of a CI/CD workflow are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;A central repository&lt;/strong&gt; where the team&amp;#x27;s code is stored and managed. In most cases, it&amp;#x27;s the version control system Git, where each code change is committed and tracked.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A build server&lt;/strong&gt; that is responsible for building the code and running tests whenever a code change is pushed to the central repository.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A deployment environment&lt;/strong&gt; where the code will be deployed, such as a staging or production environment. This can be a local server, a cloud-based platform, or in case of iOS applications a third-party service like Testflight and App Store.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;A pipeline&lt;/strong&gt; which is a set of automated steps that are executed when triggered by a code change. The pipeline can include tasks such as building the code, running tests and deploying the code.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;CI/CD workflow for an iOS application&lt;/h3&gt;&lt;p&gt;A CI/CD workflow for an iOS application might look something like this:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;A developer creates a new Git branch, develops and tests a new feature, pushes it to a shared Git repository and creates a Merge Request.&lt;/li&gt;&lt;li&gt;The Merge Request triggers a CI/CD pipeline, which builds the code and runs unit and UI tests.&lt;/li&gt;&lt;li&gt;If the build and tests are successful, another developer reviews the code. Every pushed code change resulting from the review triggers the pipeline to again build and test the code.&lt;/li&gt;&lt;li&gt;When the Merge Request is accepted and merged, a pipeline can automatically deploy the code to a staging environment, where it can be tested further by QA team members or beta testers.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;The pipeline can also be configured to perform other tasks, such as automatically releasing a new version of the app to the App Store or generating release notes.&lt;/p&gt;&lt;h3&gt;Tools to realize CI/CD workflows&lt;/h3&gt;&lt;p&gt;To realize CI/CD workflows, there are many tools available, for example Jenkins, CircleCI, TeamCity, Bamboo, GitLab CI, Travis CI, Codemagic and more.&lt;/p&gt;&lt;p&gt;These are just a few examples of CI/CD tools. There are many other tools available and the right choice depends on specific needs and requirements for a team.&lt;/p&gt;</content:encoded><category>continuous integration</category><category>ios</category></item><item><title><![CDATA[How to identify parts of speech and names in a text with Swift]]></title><description><![CDATA[Learn to use the linguistic tagging possibilities provided by the Natural Language framework in iOS.]]></description><link>https://tanaschita.com/20230515-natural-language-framework-linguistic-tags</link><guid isPermaLink="false">https://tanaschita.com/20230515-natural-language-framework-linguistic-tags</guid><pubDate>Sun, 14 May 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As we learned in the &lt;a target=&quot;_blank&quot; href=&quot;/20230508-machine-learning-with-natural-language-framework/&quot;&gt;developer guide on the Natural Language framework&lt;/a&gt;, it supports linguistic tagging. Linguistic tagging, also known as part-of-speech tagging, is the process of assigning grammatical tags to words in a text based on their role and function within a sentence.&lt;/p&gt;&lt;p&gt;We could use linguistic tagging for example to identify parts of speech in a text like verbs and nouns, or names like people names, place names or organization names in texts.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how it works.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The linguistic tagging functionality is provided by the Natural Language framework&amp;#x27;s &lt;code&gt;NLTagger&lt;/code&gt; type. The tagger is initialized with a scheme of type &lt;code&gt;NLTagScheme&lt;/code&gt; which specifies what kind of tags should be used for classification.&lt;/p&gt;&lt;p&gt;For example, we could use &lt;code&gt;.tokenType&lt;/code&gt; as a scheme, which would classify parts of a text with the following tags: &lt;code&gt;.word&lt;/code&gt;, &lt;code&gt;.punctuation&lt;/code&gt;, &lt;code&gt;.whitespace&lt;/code&gt; and &lt;code&gt;.other&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Another example for a scheme is &lt;code&gt;.lexicalClass&lt;/code&gt;, which classifies parts of a text with the tags &lt;code&gt;.noun&lt;/code&gt;, &lt;code&gt;.verb&lt;/code&gt;, &lt;code&gt;.adjective&lt;/code&gt;, &lt;code&gt;.adverb&lt;/code&gt; etc.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at the following function which extracts all verbs from a text:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func getVerbs(from text: String) -&amp;gt; [String] {
    let tags = tags(for: text, tagScheme: .lexicalClass)
    return tags.filter { $0.tag == .verb }.map { $0.word }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The function uses the following helper function which creates pairs of words and their tags based on the provided text and scheme.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func tags(for text: String, tagScheme: NLTagScheme) -&amp;gt; [(word: String, tag: NLTag)] {
    var taggedWords: [(String, NLTag)] = []

    let tagger = NLTagger(tagSchemes: [tagScheme])
    tagger.string = text
    tagger.enumerateTags(in: text.startIndex..&amp;lt;text.endIndex,
                            unit: .word,
                            scheme: tagScheme,
                            options: [.omitPunctuation, .omitWhitespace]) { tag, tokenRange in
        if let tag = tag {
            taggedWords.append((String(text[tokenRange]), tag))
        }
        return true
    }
    return taggedWords
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can use the same helper function to extract other words, for example names. The following code extracts place names from a given text:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func getPlaceNames(from text: String) -&amp;gt; [String] {
    let tags = tags(for: text, tagScheme: .nameType)
    return tags.filter { $0.tag == .placeName }.map { $0.word }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;#x27;s basically it. The &lt;code&gt;NLTagger&lt;/code&gt; makes it really easy for us to identify parts of speech in a text.&lt;/p&gt;&lt;p&gt;Check out the official &lt;code&gt;NLTagScheme&lt;/code&gt; documentation to learn more about supported classification possibilities.&lt;/p&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to detect the language of a text with the Natural Language framework in Swift]]></title><description><![CDATA[Learn to use the language recognizer of the Natural Language framework.]]></description><link>https://tanaschita.com/20230501-detect-language-with-natural-language-framework</link><guid isPermaLink="false">https://tanaschita.com/20230501-detect-language-with-natural-language-framework</guid><pubDate>Sun, 30 Apr 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If we need to detect a language of some text in an iOS application, for example some user input, the Natural Language framework comes in handy. It provides the &lt;code&gt;NLLanguageRecognizer&lt;/code&gt; type which we can use for that purpose.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;When using the language recognizer, we get the most likely language for a piece of text or a set of possible languages with their probabilities.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func language(for text: String) -&amp;gt; NLLanguage? {
    let recognizer = NLLanguageRecognizer()
    recognizer.processString(text)
    return recognizer.dominantLanguage
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then use the optained language to switch over the languages:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;guard let language = language(for: text) else { return }
switch language {
case .english:
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To get a set of languages with their probabilities, we can use the &lt;code&gt;languageHypotheses(withMaximum:)&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func languages(for text: String) -&amp;gt; [NLLanguage: Double] {
    let recognizer = NLLanguageRecognizer()
    recognizer.processString(text)
    return recognizer.languageHypotheses(withMaximum: 5)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To process another text with the same recognizer, we need to call its &lt;code&gt;reset()&lt;/code&gt; method to return it to its initial state:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;recognizer.reset()
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Developer guide on App Clips for iOS]]></title><description><![CDATA[Learn how support App Clips to allow users to perform a specific task without installing the full iOS application.]]></description><link>https://tanaschita.com/20230424-app-clips</link><guid isPermaLink="false">https://tanaschita.com/20230424-app-clips</guid><pubDate>Sun, 23 Apr 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Announced at WWDC 2020, an App Clip is a lightweight version of an app that enables users to perform a specific task without installing the full app, i.e. App Clips allow users who do not have downloaded the app to still use its functionality.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how App Clips work and how we can implement them in our iOS applications.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;How do App Clips work?&lt;/h3&gt;&lt;p&gt;App Clips can be launched with so called invocation URLs, for example, by scanning an NFC tag, QR code, App Clip code or tapping on a link.&lt;/p&gt;&lt;p&gt;As soon as a user opens an App Clip URL, the system at first presents a so called App Clip experience, which is a card showing an App Clip summary and an action button to open the App Clip. If the user chooses to open it, the App Clip binary is downloaded and launched. The invocation URL is passed in, so it can be handled as a deep link if needed.&lt;/p&gt;&lt;p&gt;In case the according iOS application is already installed on the user&amp;#x27;s device, it gets opened instead of the App Clip.&lt;/p&gt;&lt;p&gt;If an App Clip is not launched for several days, it gets deleted by the system.&lt;/p&gt;&lt;h3&gt;Supporting App Clips in an iOS application&lt;/h3&gt;&lt;p&gt;To support App Clips in an iOS application, the following general steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Adding an App Clip target&lt;/li&gt;&lt;li&gt;Developing the App Clip which includes sharing assets and code between the iOS app and the App Clip and handling of the invocations&lt;/li&gt;&lt;li&gt;Associating the App Clip with the corresponding website&lt;/li&gt;&lt;li&gt;Configuring the App Clip experience in App Store Connect&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s go through those steps in more detail.&lt;/p&gt;&lt;h3&gt;1. Adding an App Clip target&lt;/h3&gt;&lt;p&gt;To add an App Clip target to an iOS project, we can use Xcode&amp;#x27;s App Clip template.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1454px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1fcfa499bbf931a7c6c6fe8ba2f22dbf/2accf/20230424-app-clip-template.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:70.93333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACLElEQVQ4y31T2W7bMBDUdxRJbO2SEg9JFKXYkQ/FSdzWRVO06Jf0reh7P3yKpWzHCYI+ENzhLod7DDNf1diOI4bVGsP4hOVqi2EYsNlsEUIL8dd1g6YJqOoGzlcojYUxBkW7go9LcOlR+ABbR2TiHO9HbNYDht0Bw/iIGFvE2KHve7QxInaTLWchBDjv4b2HWe7RDDsoH2HiHep+QKaUhvU1bFzDOZeCiRVy4ncXMYOVSjFMOYgYShfpjFlNhDM2+FA9YE46XWKl/7942umIhVyWEGdiSFm73X3q14mQLgLpPcxvsRBqZHK563tsNmsYaxOZOJSaSklL7FdYLqsjPsXqKUMxXN3CtKupF/Ji4UAmTH1RCrlbIvfDGbNpwIU7Yg2yLVibKUN5baYrXMWvIFWCiZCbFvNqnciZ5riJ33Dd/0w+IsK1vcOsDAkL4azdIy9DyjxjNZXQNlUiULpMElncdkl385ygD7+hnv8i5wJFUWK/32Oz3aY+trHDYnGb2masm6YsmUlWaWrMIG0nzMeSBWt7nmQTWriqfpnw5VAkOzIN5t0XsC7BPJV80zxM5OkRlewTgWT9Wl4FSBVH2SiNJoSUtpQoh/L1xnGbZEQXMrrU4kn8yT+/Bs2uoJiQGWMRux4hdtBFmQJ1YWCdP5f4VthyJn75fqWrwYsDiuEZVA/IJKuPnz7j4fEpffqTWM8CP4mX1RnL3rQRt30PGxbID79Qfv+D2fIH/gHBxJ4TgM1k7gAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s App Clip template.&quot; title=&quot;Xcode&amp;#x27;s App Clip template.&quot; src=&quot;/static/1fcfa499bbf931a7c6c6fe8ba2f22dbf/2accf/20230424-app-clip-template.png&quot; srcSet=&quot;/static/1fcfa499bbf931a7c6c6fe8ba2f22dbf/12d18/20230424-app-clip-template.png 375w,/static/1fcfa499bbf931a7c6c6fe8ba2f22dbf/44507/20230424-app-clip-template.png 750w,/static/1fcfa499bbf931a7c6c6fe8ba2f22dbf/2accf/20230424-app-clip-template.png 1454w&quot; sizes=&quot;(max-width: 1454px) 100vw, 1454px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s App Clip template.&lt;/div&gt;&lt;p&gt;The template creates a structure for the App Clip which is similar to our app&amp;#x27;s structure.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a641311773768e5c09c84b634009a4b4/adaea/20230424-app-clip-example.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:55.733333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAACK0lEQVQoz2WS6VIaQRhFeRajMMMwW/cwK8goQtgXgQFjiAsaYxRDTKxUnv6kGChKKj/On66u07e/72ZG/Q69cY/hcMzgfMR0dsFoPCGMG5zWWtQHnzluJpSbCU54QhSViU+qhKWYqBwThmVcN8D1NmSKtRHJ9DIVDYbnDIcjBoNzpFfBlAFe3OW0c0GlNUMWIyxLousC05RomsnBQZbDQ4UPHzZk3CDi6suc70/PqTRJZnQ6PSzhEZkhru0ibIfskUpOKZDXBJpmk0+xUPNrzB0ZW0gmkzGPT8tUOkmmtDvdrTAgDM6wKg28oIrvnqAVbLKKQU41t7J9MtKLSOY3PC9fuP/6QKfbo9XuYgsPVTGRIqJS6RIHdUK/jm1HSMPB1AU51UDdihXVTMn4QYmrmwUvq5/cLu7o9vo0W22E9NMUWUXHMFyq5Q69eEY7HtEotfBEBcuM0AoiFe0SlqKI++s5b3/+8uv3Gw8Pj4wn003CvEWhINCNIrZZROgSqTsYuoOat9E0uUu3Exa9gOn8mueXFYu7e8bJlP5giHSCzet5iyNFJ5vT09kdqQY5xdib255QCMl0lvDtacnN7R2X8/kmofQJjYCTsE6p2qVY6yHiBqWwRiVqEvpnOLL8/5f9dW1uFixXr/xYvTL7dJnO0CmGaTWk5eP5x4iggm65mNq6h87eIt6nzAhZpP6xRX80YZgyptXppsL1hfUm14vJ5XSUbV3WZ+9TvU/4Dwv9d7hZxIXUAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;App Clip&amp;#x27;s structure.&quot; title=&quot;App Clip&amp;#x27;s structure.&quot; src=&quot;/static/a641311773768e5c09c84b634009a4b4/2e9ed/20230424-app-clip-example.png&quot; srcSet=&quot;/static/a641311773768e5c09c84b634009a4b4/12d18/20230424-app-clip-example.png 375w,/static/a641311773768e5c09c84b634009a4b4/44507/20230424-app-clip-example.png 750w,/static/a641311773768e5c09c84b634009a4b4/2e9ed/20230424-app-clip-example.png 1500w,/static/a641311773768e5c09c84b634009a4b4/adaea/20230424-app-clip-example.png 1758w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;App Clip&amp;#x27;s structure.&lt;/div&gt;&lt;p&gt;Additionally, it creates the correct entitlements and a scheme to build and run the App Clip and its tests. It also adds a new build phase for the app target that embeds the App Clip in the app.&lt;/p&gt;&lt;h3&gt;2. Developing the App Clip&lt;/h3&gt;&lt;p&gt;After adding the App Clip target, we can directly try out to build and run it by choosing the created scheme.&lt;/p&gt;&lt;p&gt;When it succeeds, we can start sharing code and assets between the App Clip and the actual iOS app. Sharing code with an App Clip works just like with any other target by selecting target memberships of a file in Xcode&amp;#x27;s File Inspector.&lt;/p&gt;&lt;p&gt;For iOS 16 devices, the uncompressed App Clip binary can be up to 15 MB in size, for iOS 15 and earlier up to 10 MB.&lt;/p&gt;&lt;p&gt;In cases we have to differentiate between the iOS app and its App Clip in code, we can use the &lt;code&gt;Active Compilation Conditions&lt;/code&gt; build setting which can be configured in the App Clip&amp;#x27;s target build settings.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1194px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5f262e42f8804d51ad9ae3b7a038c25d/6428d/20230424-app-clip-compilation-conditions.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:21.866666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAuElEQVQY02WQ2QqDMBBF/RJxi3s1xrjUWtcutEXo///MLRNRWvpwuDDJPTCjRYcMTTsohKxQVCfUTYc4EdANB5bjw2bBjuvHcNzwb76h0QN94KJA18+Q5VFJKWXZwA+TvUyZcom8OCLLSwV1f4Sm7cG0fVUkwWt5o+1GcFGiqluEMYfjBmBepIQJl3g8F8yXO8bppuYkZd6KEpKZkgrDdIXIK+gmg2G5sNm6wbYmnejcT0izYpd98wEXf3oMISfYIQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Active Compilation Conditions.&quot; title=&quot;Active Compilation Conditions.&quot; src=&quot;/static/5f262e42f8804d51ad9ae3b7a038c25d/6428d/20230424-app-clip-compilation-conditions.png&quot; srcSet=&quot;/static/5f262e42f8804d51ad9ae3b7a038c25d/12d18/20230424-app-clip-compilation-conditions.png 375w,/static/5f262e42f8804d51ad9ae3b7a038c25d/44507/20230424-app-clip-compilation-conditions.png 750w,/static/5f262e42f8804d51ad9ae3b7a038c25d/6428d/20230424-app-clip-compilation-conditions.png 1194w&quot; sizes=&quot;(max-width: 1194px) 100vw, 1194px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Active Compilation Conditions.&lt;/div&gt;&lt;p&gt;With that setup, we can use it as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#if !APPCLIP
// App code
#else
// App Clip code
#endif
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The invocation URL is passed in as a user activity. To handle the URL in a SwiftUI application after the App Clip is launched, we can use the &lt;code&gt;onContinueUserActivity(_:perform:)&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;import SwiftUI

@main
struct ExampleAppClipApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
                    guard let incomingURL = activity.webpageURL else { return }
                    guard let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else { return }
                    // Navigate to a certain part of the App Clip based in the URL
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the passed in user activity has a &lt;code&gt;webpageURL&lt;/code&gt; property which we can use to show customized content to the user if needed.&lt;/p&gt;&lt;p&gt;Since the main application is launched instead of the App Clip if installed, similar code should be added there too.&lt;/p&gt;&lt;h3&gt;3. Associating the App Clip with the corresponding website&lt;/h3&gt;&lt;p&gt;The App Clip will be launched if a user opens an invocation URL. Before the system launches the App Clip, it verifies the app&amp;#x27;s entitlement and the server&amp;#x27;s &lt;code&gt;Apple App Site Association&lt;/code&gt; file to make sure that the App Clip and the website are associated.&lt;/p&gt;&lt;p&gt;To associate an App Clip with a website, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add an Associated Domains entitlement&lt;/strong&gt;. For that, we need to enable the &lt;code&gt;Associated Domains&lt;/code&gt; capability in Xcode&amp;#x27;s project settings and to specify the invocation URL&amp;#x27;s domain within the &lt;code&gt;Associated Domains Entitlement&lt;/code&gt;: &lt;code&gt;appclips:&amp;lt;fully qualified domain&amp;gt;&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add an &lt;code&gt;Apple App Site Association&lt;/code&gt; file on the domain&amp;#x27;s server.&lt;/strong&gt; For that, an Apple App Site Association file needs to be configured and uploaded to the server&amp;#x27;s &lt;code&gt;.well-known&lt;/code&gt; directory with the following content:&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;appclips&amp;quot;: {
      &amp;quot;apps&amp;quot;: [&amp;quot;PREFIX.com.tanaschita.ExampleApp.Clip&amp;quot;]
  }
  ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check out this &lt;a target=&quot;_blank&quot; href=&quot;/20220725-quick-guide-on-associated-domains-in-ios/&quot;&gt;guide on associated domain files for iOS development&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;4. Configure the App Clip experience in App Store Connect&lt;/h3&gt;&lt;p&gt;After uploading a build that contains an App Clip to App Store Connect, we can configure App Clip experiences in App Store Connect. This includes configuring the URL which will launch the App Clip experience card, adding meta data like title, subtitle and image and choosing a call to action.&lt;/p&gt;&lt;p&gt;Additionally, we can choose between default and advanced experiences. With a default App Clip experience, we can add a Smart App Banner to the corresponding website that launches the App Clip. This will also enable invocations from links that are shared in Messages.&lt;/p&gt;&lt;p&gt;To support additional invocations such as scanning an App Clip Code or an NFC tag, we need to configure an advanced App Clip experience.&lt;/p&gt;&lt;p&gt;We can configure multiple App Clip experiences for one App Clip, for example one experience for each deep link the App Clip supports.&lt;/p&gt;</content:encoded><category>associated domains</category><category>ios</category></item><item><title><![CDATA[Cheatsheet for the JSON payload of an iOS push notification]]></title><description><![CDATA[Learn the structure and configuration possibilities of a remote push notification.]]></description><link>https://tanaschita.com/20230417-cheatsheet-for-anatomy-of-ios-push-notifications</link><guid isPermaLink="false">https://tanaschita.com/20230417-cheatsheet-for-anatomy-of-ios-push-notifications</guid><pubDate>Sun, 16 Apr 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When configuring the JSON payload of an iOS push notification, there are a lot of configurations options we can choose from. This guide serves as a reference and cheatsheet for those configuration options.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Basic JSON payload structure&lt;/h3&gt;&lt;p&gt;The payload for a remote notification is a JSON dictionary with the following basic structure:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;aps&amp;quot;: {
    &amp;quot;alert&amp;quot;: &amp;quot;Title of the push notification&amp;quot;,
    &amp;quot;sound&amp;quot;: &amp;quot;default&amp;quot;,
    // More configuration keys and values
  },
  &amp;quot;customKey1&amp;quot;: &amp;quot;Some content&amp;quot;,
  &amp;quot;customKey2&amp;quot;: &amp;quot;Some other content&amp;quot;,
  // More custom keys and values.
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the JSON is a dictionary which can contain custom keys for example to provide a deep link or additional information our app may need.&lt;/p&gt;&lt;p&gt;To customize the push notification, we can use the &lt;code&gt;aps&lt;/code&gt; dictionary as also shown above. Let&amp;#x27;s explore those keys and their effects on the push notification.&lt;/p&gt;&lt;h3&gt;The alert key&lt;/h3&gt;&lt;p&gt;The value for the &lt;code&gt;alert&lt;/code&gt; key can either be a &lt;code&gt;String&lt;/code&gt; or a &lt;code&gt;Dictionary&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;aps&amp;quot;: {
    &amp;quot;alert&amp;quot;: &amp;quot;Title of the push notification&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The configuration above produces the following push notification:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:682px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/79d86285612818de30039ef63ed011ef/e899a/20220321_push_alert_string.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:21.866666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAABJ0lEQVQY0x3M3y8CAQDA8fsLwySrjOTqCBXaWirqurq6un4cSaqpM7cZaSPzYGvz6s1sHtHmmZnZvH1tPn/AR3hIOHg0de7bOpaWppmKYaXD3CkSI8XHKCtSjYVYXE/iDe7gCabwhGS8mwpiRMUf1QgkysRKLaScgfDcb/H188v4bUy9YqCks6TiuyjbcbRkgt5Bk3azy16jh96wKB6eUTo6R2/1KXcGGOaQ2skQw7pB7V0iPF2d8v35wfvrC2anS7FQplqsUstrGKpKRS1QyFdIyDoxuUpUrqHUTMrtAZX/8Jq6dUt7MCJ7fIFglnKoqQw+KYzbs8rsfIAZ9zIOlx+Hy4fd6WfKKTHhXMHmDGBzr2GbCzK5sMG0N4JdjCJuZZD3uyzJOn/b89FIKn0kXQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Push notification with title only.&quot; title=&quot;Push notification with title only.&quot; src=&quot;/static/79d86285612818de30039ef63ed011ef/e899a/20220321_push_alert_string.png&quot; srcSet=&quot;/static/79d86285612818de30039ef63ed011ef/12d18/20220321_push_alert_string.png 375w,/static/79d86285612818de30039ef63ed011ef/e899a/20220321_push_alert_string.png 682w&quot; sizes=&quot;(max-width: 682px) 100vw, 682px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Push notification with title only.&lt;/div&gt;&lt;p&gt;Alternatively, we can specify the notification&amp;#x27;s title, subtitle and body:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;aps&amp;quot;: {
    &amp;quot;alert&amp;quot;: {
        &amp;quot;title&amp;quot;: &amp;quot;Title of the notification&amp;quot;,
        &amp;quot;subtitle&amp;quot;: &amp;quot;Subtitle of the notification&amp;quot;,
        &amp;quot;body&amp;quot;: &amp;quot;The content of the notification.&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which results in the following output:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:684px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6c853ee21cba9f6caa57fd29b9945a1c/921db/20220321_push_alert_dictionary.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:22.400000000000002%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAABFElEQVQY0x3OSU7CYABA4R7XxJmh/J0oFWjLVLEtM9pUxihERQ0bF7pxZWI8ggMr4AzP6O6tvjzpzd3h4+aSl0nEqBEQ2Db9UoGxozErqYxsQc0ykXWXpFFFc7soTgfV7aFXLjBPYyx/SKl/jQhjpPdpi/V6y/fnF02/STqlYuoW+ayFpWjoQkfOGBwmdY4Vm2y5S7l9RbU7o9ab48cLguED4XSJN75Hep0N2Gy2rH5WeF5IRrFQVIuscfIPZ408aWGRUG12Uzn25AKiEOA2Jnj9OUG8oDVdEt09U58+IrVrNc57MX7YJ1esI8wKac0hIfLIuvPff2dJvcKB4nKkVdgXLqLYJOdFFIMBZ9Gc6PYJszPkF8+R2ESdgHy2AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Push notification with title, subtitle and body.&quot; title=&quot;Push notification with title, subtitle and body.&quot; src=&quot;/static/6c853ee21cba9f6caa57fd29b9945a1c/921db/20220321_push_alert_dictionary.png&quot; srcSet=&quot;/static/6c853ee21cba9f6caa57fd29b9945a1c/12d18/20220321_push_alert_dictionary.png 375w,/static/6c853ee21cba9f6caa57fd29b9945a1c/921db/20220321_push_alert_dictionary.png 684w&quot; sizes=&quot;(max-width: 684px) 100vw, 684px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Push notification with title, subtitle and body.&lt;/div&gt;&lt;p&gt;There is also the possibility to use localized keys instead:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      &amp;quot;alert&amp;quot; : {
         &amp;quot;title-loc-key&amp;quot; : &amp;quot;notification.newMessage.title&amp;quot;,
         &amp;quot;title-loc-args&amp;quot; : [ &amp;quot;argument1&amp;quot;, &amp;quot;argument2&amp;quot;],
         &amp;quot;subtitle-loc-key&amp;quot; : &amp;quot;notification.newMessage.subtitle&amp;quot;,
         &amp;quot;body-loc-key&amp;quot; : &amp;quot;notification.newMessage.body&amp;quot;,
      }
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The keys stored in &lt;code&gt;Localizable.strings&lt;/code&gt; will automatically be used.&lt;/p&gt;&lt;h3&gt;The badge key&lt;/h3&gt;&lt;p&gt;The badge key is used to specify the number that will appear on the app&amp;#x27;s icon as a badge.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;aps&amp;quot; : {
    ...
    &amp;quot;badge&amp;quot; : 0
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The value &lt;code&gt;0&lt;/code&gt; removes the current badge.&lt;/p&gt;&lt;h3&gt;The sound key&lt;/h3&gt;&lt;p&gt;The sound key is used to specify the sound that will play when the notification is received which can either be a default, a custom or a critical sound.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Default sound&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      ...
      &amp;quot;sound&amp;quot; : &amp;quot;default&amp;quot;
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example above plays the system sound.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Custom sound&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      ...
      &amp;quot;sound&amp;quot; : &amp;quot;applause.wav&amp;quot;
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown above, we can specify a sound file in the app&amp;#x27;s main bundle in .aiff, .wav or .caf file format to be played when the notification is shown.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Critical sound&lt;/em&gt;&lt;/p&gt;&lt;p&gt;A critical sound plays even if Do Not Disturb is turned on or the device is muted. Only apps approved for the critical alerts entitlement are allowed to send critical alerts. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      ...
      &amp;quot;sound&amp;quot;: {
        &amp;quot;critical&amp;quot; : 1,
        &amp;quot;name&amp;quot;: &amp;quot;default&amp;quot;, // or name of a sound file in the app&amp;#x27;s main bundle
        &amp;quot;volume&amp;quot;: 1 // value between 0 (silent) and 1 (full volume)
      }
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The thread-id key&lt;/h3&gt;&lt;p&gt;An identifier for grouping related notifications, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      ...
      &amp;quot;thread-id&amp;quot;: &amp;quot;com.tanaschita.newArticle&amp;quot;
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The iOS system will automatically sum up notifications with the same thread-id into a group when showing them in the notification center.&lt;/p&gt;&lt;h3&gt;The category key&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;category&lt;/code&gt; key can be used to add custom actions to the notification.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      ...
      &amp;quot;category&amp;quot; : &amp;quot;drinkingReminderCategory&amp;quot;
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checkout this article on &lt;a target=&quot;_blank&quot; href=&quot;/20220321-how-to-add-and-handle-actions-for-ios-notifications/&quot;&gt;how to add custom actions for iOS notifications&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;The content-available key&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;content-available&lt;/code&gt; key declares the notification as a background notification.&lt;/p&gt;&lt;p&gt;A background notification doesn&amp;#x27;t display an alert, play a sound or show a badge. It wakes the app in the background and gives it time to initiate downloads and update its content.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      &amp;quot;content-available&amp;quot; : 1
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check out the official documentation on &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot;&gt;pushing background updates to your app&lt;/a&gt; to learn more about background notifications.&lt;/p&gt;&lt;h3&gt;The mutable-content key&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;mutable-content&lt;/code&gt; key can be used if we need to modify the payload of a remote notification before it&amp;#x27;s shown to the user, for example to decrypt data or to incorporate additional content based on data within the application.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      &amp;quot;mutable-content&amp;quot; : 1,
      ...
   },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Modifying a remote notification requires a notification service app extension, which receives the contents of the notification before is is displayed to the user to be able update the notification payload.&lt;/p&gt;&lt;p&gt;Check out the official documentation on &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications&quot;&gt;modifying content in newly delivered notifications&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h4&gt;The interruption-level key&lt;/h4&gt;&lt;p&gt;The interruption-level key represents the importance and delivery timing of a notification:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      &amp;quot;interruption-level&amp;quot; : &amp;quot;active&amp;quot;,
      ...
   },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The following values are available:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;active&lt;/code&gt; - presents the notification immediately, lights up the screen, can play a sound.&lt;/li&gt;&lt;li&gt;&lt;code&gt;critical&lt;/code&gt; - presents the notification immediately, lights up the screen, bypasses the mute switch to play a sound.&lt;/li&gt;&lt;li&gt;&lt;code&gt;passive&lt;/code&gt; - adds the notification to the notification list without lighting up the screen or playing a sound.&lt;/li&gt;&lt;li&gt;&lt;code&gt;timeSensitive&lt;/code&gt; - similar to active notifications, but can break through system controls such as Notification Summary and Focus. The user can turn off the ability for time sensitive notification interruptions.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;The relevance-score key&lt;/h3&gt;&lt;p&gt;The relevance score value is a number between 0 and 1, that the system uses to sort the notifications from the same iOS application.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
   &amp;quot;aps&amp;quot; : {
      &amp;quot;relevance-score&amp;quot; : 1,
      ...
   },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The highest score gets featured in the notification summary.&lt;/p&gt;</content:encoded><category>notifications</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to scan QR codes with VisionKit for iOS]]></title><description><![CDATA[Learn how to use the DataScannerViewController to scan QR codes.]]></description><link>https://tanaschita.com/20230410-how-to-scan-qr-codes-with-visionkit-for-ios</link><guid isPermaLink="false">https://tanaschita.com/20230410-how-to-scan-qr-codes-with-visionkit-for-ios</guid><pubDate>Sun, 09 Apr 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting with iOS 16, Apple introduced a data scanner in VisionKit, which allows us to to scan data in the live video stream from the iOS camera. Besides different text types, it supports recognition for various barcode types including automatic item highlighting.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how we can implement the data scanner functionality for QR codes.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Overview&lt;/h3&gt;&lt;p&gt;To implement a data scanner for QR codes, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Adding camera usage description for user permission.&lt;/li&gt;&lt;li&gt;Creating a data scanner view controller.&lt;/li&gt;&lt;li&gt;Using delegate methods to react to recognized QR codes.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s see how to implement those steps in more detail.&lt;/p&gt;&lt;h3&gt;1. Adding camera usage description for user permission&lt;/h3&gt;&lt;p&gt;To be able to use the data scanner, the user needs to grant permission for camera usage. We can configure the reason for using the camera by adding the &lt;code&gt;NSCameraUsageDescription&lt;/code&gt; key to the target&amp;#x27;s information property list.&lt;/p&gt;&lt;h3&gt;2. Creating a data scanner view controller&lt;/h3&gt;&lt;p&gt;When initializing the data scannner, we can specify the item types to scan, quality level, highlighting and more:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let viewController = DataScannerViewController(
    recognizedDataTypes: [.barcode(symbologies: [.qr])],
    qualityLevel: .balanced,
    recognizesMultipleItems: false,
    isHighFrameRateTrackingEnabled: false,
    isHighlightingEnabled: true)
viewController.delegate = self

try? viewController.startScanning()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As shown in the code above, we pass in &lt;code&gt;[.barcode(symbologies: [.qr])]&lt;/code&gt; to scan QR codes. Besides QR codes, the scanner view controller supports various other barcode symbologies. Checkout the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/vision/vnbarcodesymbology&quot;&gt;official documentation on supported barcode symbologies&lt;/a&gt; to learn more.&lt;/p&gt;&lt;p&gt;Besides various barcode types, the data scanner view controller supports text recognition for different text content types like email address, phone number, street address, flight numbers and more.&lt;/p&gt;&lt;p&gt;For example, we could pass in &lt;code&gt;[.text([], .emailAddress)]&lt;/code&gt; as &lt;code&gt;recognizedDataTypes&lt;/code&gt; to recognize email addresses.&lt;/p&gt;&lt;h3&gt;3. Using delegate methods to react to recognized QR codes&lt;/h3&gt;&lt;p&gt;The scanner delegate provides methods to react to recognized items or to user&amp;#x27;s interactions such as tapping on an item. The following delegate method is called when the scanner recognizes new items:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
    guard let item = allItems.first else { return }
    switch item {
    case .barcode(let recognizedCode):
        print(recognizedCode.payloadStringValue)
    default:
        break
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&amp;#x27;s basically it. The &lt;code&gt;DataScannerViewController&lt;/code&gt; makes is really easy to implement QR code scanning functionality in iOS applications.&lt;/p&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Get started with Create ML to train a machine learning model in iOS]]></title><description><![CDATA[Learn how to use Create ML to create machinge learning models.]]></description><link>https://tanaschita.com/20230403-train-ml-model-with-create-ml-ios</link><guid isPermaLink="false">https://tanaschita.com/20230403-train-ml-model-with-create-ml-ios</guid><pubDate>Sun, 02 Apr 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As mentioned in this &lt;a target=&quot;_blank&quot; href=&quot;/20230313-machine-learning-ios-core-ml/&quot;&gt;developer guide on machine learning for iOS with Core ML&lt;/a&gt;, Apple&amp;#x27;s &lt;code&gt;Create ML&lt;/code&gt; app allows us to train a model.&lt;/p&gt;&lt;p&gt;Create ML is part of Xcode&amp;#x27;s tool chain and allows us to train machinge learning models without requiring extensive knowledge of machine learning, for example allowing us to drag and drop datasets, choose an algorithm, and configure the model parameters through a graphical interface.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;After opening Create ML, the application presents different templates to choose from. They can be used to train models for tasks such as image classification, object detection, text and sound classification, classification and regression of tabular data and more.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/bdd7a726188f353e9d2cd50f9a82ca36/411bb/20230417-create-ml-templates.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:66.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABYlAAAWJQFJUiTwAAACjUlEQVQ4y32SzY4bRRSF+wV5C56AF+ApWCCEkBA7hpBFFkFZEM0k8xNPbLfdtgfDkEnbjt0/1dX/1V1d1R/qJhIThDjSUZ27Obfuvcf58qc/+ewbwec/RHwIYspEEJ8CaqUY0fcj+0fvqD/iH/U3nKcXW757vubJxRYpC7ba8q610PWk1rC1ltZasJbKWsxgaAx3dc2xacB0BLJmF1V0usW5nnjMJm+48xYUMuXnw44Xu/fQal4FAV+s1xyLAlWWfLvb8VuSQNdxFkfcyhSMZu2nXK1DqiLHeXWz4vz1LZvVml+WGV9fKL76Nef5piMUIe7DPWmWoWqF5x8IkwSlaupEksYxVV2PzZIopKwqnIsbj5fn1yxcj0NUcBd0bIMOURmMMujMjDszpgMtoDcYY4magryt6Y0lbxV5U2ONwbmcLLm8njCbLSmzhMtNwJuVD2jEH5r7FyWmt7RtThr+SNNITGt5JlZMpQ9tj5cfOIsXKKVwXl6vuLy6xXU94liQpDkfTiFVVVEVFdExHnXbtghxJE3T8SeRFARRhOkMeVXye7hHa41z83bJZDJj4a5IEomqK8qyoGkaVNNQqHrUg2FRN9S1ou8tjVLjXofYdF1HWymstTjufIE7X7KYe+SlIp+eU2ymGEClCdn8NV2j0LojW1xRx6dPs9d/mkRnPl8yny1H0zQrkPv3nN7d02hNlgh27oSqLMb9+G+vEMfDaNg/CvljOrPpgvlAd0UQhEQiIYxizDCG1pSqGUcyxlA1LU2r+T84nrdh5W3YbLY8PPj4vs9+v0cIQZZlSCnHHQ5RGY4xGA8N/k398XWGy4ZhNB5EypQ0zUYOtRDJyEH/Vz0wlZJTJPn+puJsWvIXhlTReDIs3wUAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Create ML templates&quot; title=&quot;Create ML templates&quot; src=&quot;/static/bdd7a726188f353e9d2cd50f9a82ca36/2e9ed/20230417-create-ml-templates.png&quot; srcSet=&quot;/static/bdd7a726188f353e9d2cd50f9a82ca36/12d18/20230417-create-ml-templates.png 375w,/static/bdd7a726188f353e9d2cd50f9a82ca36/44507/20230417-create-ml-templates.png 750w,/static/bdd7a726188f353e9d2cd50f9a82ca36/2e9ed/20230417-create-ml-templates.png 1500w,/static/bdd7a726188f353e9d2cd50f9a82ca36/411bb/20230417-create-ml-templates.png 2082w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Create ML templates&lt;/div&gt;&lt;p&gt;As an example, we&amp;#x27;ll work with the image classification template.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s say, we want to train a model which recognizes different weather conditions like rain, sunshine, sunset and clouds in photos.&lt;/p&gt;&lt;p&gt;To be able to train the model to do that, we need a labeled dataset of photos. The data set might look as follows:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:964px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b860db62e32622af2259035daddd78ab/92a8c/20230417-create-ml-dataset.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:40.53333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABaElEQVQoz3WOXYvTQBSG8wN19V7YO0H8NbvolReyoj/EC0FEpNUVux+d1tqSTJI2k3bzMZmvR5Jli6t24GF4z/scOFEIwQVrXJIkTmvt+uycd97fp39VVQ1On3uvaZr9zp0TaeMQRctytUKVJQFwzuN8+AuPsQ7r3H5W1TXb7Q7/hxfVWof38zzkchVMWwVCF/Dd7X8IrwenuVGh3KT3/GiWa96NFB++LvlytebbsmP0s2W0OMz4l2a8aPl8rfg0yRgvbnPfRW8/Vjx5I3l2Jnl+lnB0EvPoRcbjl/lBHpwkHJ1Kjl9lPH2d8fBU7ncigkfmiu+Xcy4urxBiSqdbnDVY8x+sodis2azXFEVBnEikTGjqCucsUQgBVW4RszkzMUUIgdYaay3GmH/o532vlKIsS/I8I01T6roeuigEzzLJOf9xjZhOmUwmQ6l1R6v1sHxH22q6rkOpkizLhgulTInjmN3uZuh+A3CAUSdi0EtnAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Image classification data set&quot; title=&quot;Image classification data set&quot; src=&quot;/static/b860db62e32622af2259035daddd78ab/92a8c/20230417-create-ml-dataset.png&quot; srcSet=&quot;/static/b860db62e32622af2259035daddd78ab/12d18/20230417-create-ml-dataset.png 375w,/static/b860db62e32622af2259035daddd78ab/44507/20230417-create-ml-dataset.png 750w,/static/b860db62e32622af2259035daddd78ab/92a8c/20230417-create-ml-dataset.png 964w&quot; sizes=&quot;(max-width: 964px) 100vw, 964px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Image classification data set&lt;/div&gt;&lt;p&gt;As we can see above, the images are categorized in folders. The folder names represent the image labels.&lt;/p&gt;&lt;p&gt;After adding the data set, Create ML creates a class for each folder:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5a81ccd218e4b9268b31733ca81fb3aa/4e346/20230417-create-ml-training.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:39.46666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA4klEQVQoz11Ry47EIAzj//9yzrNz2Ja2QB5eOd20dCIhgnGME4oaMNQBONzPvYvj9QF+a0NdFyzLgnVdUWvFtm2Rk5uLMcYIvPy83xARzEFOE4eqwUyhqsHJ/ZvPMDO01lB679j3PdSP4wDPvbdwykLidEUHvGMhXSVOjELk8q5QnWAItnYXul9t8EEWEKdLFmb78s/PDkrOgCAj3cxB53RxjuOemzkgevMuQQ83HcfgLo+BU5wOw43IQ1DNIXZ+5EMw+1e7fyyx+QPIJRZc1VNoevwSTNJ8kefMvzmZm/mD+wf/VHZ+TuO3NwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Create ML training&quot; title=&quot;Create ML training&quot; src=&quot;/static/5a81ccd218e4b9268b31733ca81fb3aa/2e9ed/20230417-create-ml-training.png&quot; srcSet=&quot;/static/5a81ccd218e4b9268b31733ca81fb3aa/12d18/20230417-create-ml-training.png 375w,/static/5a81ccd218e4b9268b31733ca81fb3aa/44507/20230417-create-ml-training.png 750w,/static/5a81ccd218e4b9268b31733ca81fb3aa/2e9ed/20230417-create-ml-training.png 1500w,/static/5a81ccd218e4b9268b31733ca81fb3aa/4e346/20230417-create-ml-training.png 1748w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Create ML training&lt;/div&gt;&lt;p&gt;This is all we need to do to be able to start the training process by clicking the train button.&lt;/p&gt;&lt;p&gt;After the model training is completed, we can test the model with new data in the preview tab.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8817d7839a7f3b1267e8701a9adb49ce/95d35/20230417-create-ml-preview.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:61.86666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAAB8klEQVQoz5WT30uTURjH93dUN112k9GFsKLAKy+6iy4iCCqRQiSwHyyoCxcZA1eWRMKmBcnWiLQuVjDDcNqsVkarLCFaWxKtGa/b3ne97+Z7zif2vptrk8q+8Jzf53O+zzkchxACKQW2pFUKYbKcy6FpGoWCiqqqGHqRrAaqLjB+ajRL13UrHEsFSTIryOQk+aJEUSVG2T6kclhFWaXAy/dpPmfyJD5+JT6fJvHpB8nvGuWVlaoJgWmaOO7NLOGPlBidgkBU4n8kmf9iOzWFXae+Kdx68JzY2zSxNynCsQWGJhZxXx1FKyzbuUlphUMIibA6IFZDWumLKvBDKsNAaJq7U++YmEsSebXI9bFZes+70fViI7B+C3L1DmuTtZTjL+K4PV68viB3phcYuT3OqZPH8fZfoGQYjcBao2Kqsa4DX8895cD+Nlq2bWF3Wzsul4vTJ7rxD3ooFv/o0B5scGiaVv/Z7BN2trawcdMGNm/dwaGOI/T3nSMwcgWj2eHfgGYVGJ2M0L5rO06nk71dZzl67DA3hq8Rvh+kXCr9P3BmMszBPa2c6emku6uDvl4XN32XCQV863f4uxLxKD2d+xi4dJGhQQ/joWHGgj6ijx9S+pfDGqyyMJ/PN8Ala1V/1HUAFUWxvlMl9eaN9peVa4C/AMoiaZ1kAPQKAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Create ML preview&quot; title=&quot;Create ML preview&quot; src=&quot;/static/8817d7839a7f3b1267e8701a9adb49ce/2e9ed/20230417-create-ml-preview.png&quot; srcSet=&quot;/static/8817d7839a7f3b1267e8701a9adb49ce/12d18/20230417-create-ml-preview.png 375w,/static/8817d7839a7f3b1267e8701a9adb49ce/44507/20230417-create-ml-preview.png 750w,/static/8817d7839a7f3b1267e8701a9adb49ce/2e9ed/20230417-create-ml-preview.png 1500w,/static/8817d7839a7f3b1267e8701a9adb49ce/95d35/20230417-create-ml-preview.png 1750w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Create ML preview&lt;/div&gt;&lt;p&gt;When satisfied with the model, we can save it as a &lt;code&gt;.mlmodel&lt;/code&gt; file from the output tab and integrate it into our iOS application. To learn how to do that, check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20230313-machine-learning-ios-core-ml/&quot;&gt;machine learning for iOS with Core ML&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to change JSON keys by using Swift's CodingKey protocol]]></title><description><![CDATA[Learn how to map JSON data to a different structure in Swift.]]></description><link>https://tanaschita.com/swift-json-custom-decoding-encoding</link><guid isPermaLink="false">https://tanaschita.com/swift-json-custom-decoding-encoding</guid><pubDate>Sun, 26 Mar 2023 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Swift&amp;#x27;s &lt;code&gt;Codable&lt;/code&gt; protocol makes it easy to convert JSON data to native Swift types and the other way around. In the article on &lt;a target=&quot;_blank&quot; href=&quot;/swift-json-codingkey-protocol/&quot;&gt;how to use the CodingKey protocol&lt;/a&gt;, we explored how to use custom names for Swift properties when decoding JSON data.&lt;/p&gt;&lt;p&gt;In some cases though, we might need an even more customized solution, for example to map the JSON data to a different structure.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s say we get the following JSON data:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;username&amp;quot; : &amp;quot;tanaschita&amp;quot;,
  &amp;quot;validated&amp;quot;: true,
  &amp;quot;address&amp;quot;: {
    &amp;quot;postalCode&amp;quot;: &amp;quot;04275&amp;quot;,
    &amp;quot;city&amp;quot;: &amp;quot;Leipzig&amp;quot;,
    ...
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our application is only interested in the user&amp;#x27;s &lt;code&gt;city&lt;/code&gt; property, that&amp;#x27;s why we&amp;#x27;d like to add it directly to our &lt;code&gt;User&lt;/code&gt; struct without defining an extra &lt;code&gt;Address&lt;/code&gt; type as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct User: Codable {

    let username: String
    let isValidated: Bool
    let city: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To be able to map the data the way we want, we can provide a custom implementation of &lt;code&gt;Encodable&lt;/code&gt; and &lt;code&gt;Decodable&lt;/code&gt; to define our own encoding and decoding logic:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;struct User: Codable {

    let username: String
    let isValidated: Bool
    let city: String

    enum CodingKeys: String, CodingKey {
        case username
        case isValidated = &amp;quot;validated&amp;quot;
        case address
    }

    enum AddressCodingKeys: String, CodingKey {
        case city
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        username = try values.decode(String.self, forKey: .username)
        isValidated = try values.decode(Bool.self, forKey: .isValidated)

        let address = try values.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
        city = try address.decode(String.self, forKey: .city)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the same way, we can add our own encoding logic:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(username, forKey: .username)
    try container.encode(isValidated, forKey: .isValidated)

    var address = container.nestedContainer(keyedBy: AddressCodingKeys.self, forKey: .address)
    try address.encode(city, forKey: .city)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This possibility gives us a way to be fully independent of the JSON structure so we can create an entirely customized representation of that data in our iOS apps.&lt;/p&gt;</content:encoded><category>json</category><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Get started with NSPredicate to filter NSFetchRequest in Core Data]]></title><description><![CDATA[Get a quick overview on how to use predicates to filter fetch request results.]]></description><link>https://tanaschita.com/20230320-cheatsheet-nspredicate-fetchrequest-core-data</link><guid isPermaLink="false">https://tanaschita.com/20230320-cheatsheet-nspredicate-fetchrequest-core-data</guid><pubDate>Sun, 19 Mar 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As mentioned in this &lt;a href=&quot;/20220919-understanding-the-main-aspects-of-core-data/&quot; target=&quot;_blank&quot;&gt;guide on the main aspects of Core Data&lt;/a&gt;, predicates can be used to filter fetch request results.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in and look at some examples to understand how predicates work.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Creating predicates&lt;/h3&gt;&lt;p&gt;Predicates are represented by the &lt;code&gt;NSPredicate&lt;/code&gt; type.&lt;/p&gt;&lt;p&gt;They represent logical conditions, which we can use to filter a collection of objects. When working with Core Data, we can filter fetch request results by setting a predicate as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let usersFetchRequest = NSFetchRequest&amp;lt;User&amp;gt;(entityName: &amp;quot;User&amp;quot;)
usersFetchRequest.predicate = NSPredicate(format: &amp;quot;isVerified == YES&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or when using SwiftUI:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@FetchRequest(predicate: NSPredicate(format: &amp;quot;isVerified == YES&amp;quot;))
private var users: FetchedResults&amp;lt;User&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, we initialize a predicate with a string format. The example will fetch all users that are verified.&lt;/p&gt;&lt;p&gt;The predicate used in the example can also be written by using variable substitution as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format: &amp;quot;isVerified == %@&amp;quot;, NSNumber(value: true))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;%@&lt;/code&gt; is a format specifier and will be substituted with &lt;code&gt;NSNumber(value: true)&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Instead of referencing properties with raw strings, we could use key paths to add more safety to our code by using the &lt;code&gt;%K&lt;/code&gt; format specifier:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format: &amp;quot;%K == %@&amp;quot;, #keyPath(User.isVerified), NSNumber(value: true))
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Format specifiers&lt;/h3&gt;&lt;p&gt;As we already saw above, format specifiers allow us to use variable substitution. Examples of format specifiers are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;%@&lt;/code&gt; for objects&lt;/li&gt;&lt;li&gt;&lt;code&gt;@K&lt;/code&gt; for key paths&lt;/li&gt;&lt;li&gt;&lt;code&gt;@d&lt;/code&gt; for integers&lt;/li&gt;&lt;li&gt;&lt;code&gt;@f&lt;/code&gt; for floating numbers&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Checkout &lt;a target=&quot;\_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html&quot;&gt; Apple&amp;#x27;s official documentation&lt;/a&gt; to discover more format specifiers.&lt;/p&gt;&lt;h3&gt;Comparison predicate operators&lt;/h3&gt;&lt;p&gt;In the example above, we used &lt;code&gt;==&lt;/code&gt; as comparison operator for an exact match.&lt;/p&gt;&lt;p&gt;We could also use compare operators like &lt;code&gt;!=&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt; &lt;code&gt;&amp;lt;&lt;/code&gt; etc. which are self-explanatory. An example to filter all users who reached the age of 18 could look this way:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format: &amp;quot;age &amp;gt;= 18&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two more interesing compare operators are &lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;BETWEEN&lt;/code&gt;:&lt;/p&gt;&lt;p&gt;&lt;code&gt;IN&lt;/code&gt; can be used to provide an array of values to match. For example, to filter all users of certain ages:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format:&amp;quot;age IN %@&amp;quot;, agesArray)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;BETWEEN&lt;/code&gt; can be used to provide a range to match:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format:&amp;quot;age BETWEEN {12, 17}&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When working with strings, we can also use comparison operators &lt;code&gt;BEGINSWITH&lt;/code&gt;, &lt;code&gt;CONTAINS&lt;/code&gt; or &lt;code&gt;ENDSWITH&lt;/code&gt;. For example, to match all users which last names begin with a t:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format:&amp;quot;lastName BEGINSWITH &amp;#x27;t&amp;#x27;&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Compound predicate operators&lt;/h3&gt;&lt;p&gt;To combine multipe predicates, we can use standard logical operators:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;AND&lt;/code&gt; or &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;OR&lt;/code&gt; or &lt;code&gt;||&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;NOT&lt;/code&gt; or &lt;code&gt;!&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;NSPredicate(format:&amp;quot;age &amp;gt;= 18 AND isVerified == YES&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;While &lt;code&gt;NSPredicate&lt;/code&gt; is a powerful filter mechanism, it does come with downsides when used in Swift. Since queries are written as raw strings, we don&amp;#x27;t get any compile-time safety, mistakes are only noticed at runtime.&lt;/p&gt;&lt;p&gt;In this article, we only discovered the basics of predicates. To dive deeper, check out &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001798-SW1&quot;&gt;Apple&amp;#x27;s official programming guide&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Developer guide on machine learning for iOS with Core ML]]></title><description><![CDATA[Learn basic machine learning concepts and how to use machine learning in iOS.]]></description><link>https://tanaschita.com/20230313-machine-learning-ios-core-ml</link><guid isPermaLink="false">https://tanaschita.com/20230313-machine-learning-ios-core-ml</guid><pubDate>Sun, 12 Mar 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting with iOS 11, Apple introduced Core ML which abstracts the complexity of machine learning allowing us to utilize it in our iOS applications.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at basic machine learning concepts and how we can use Core ML to implement it in an iOS application.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;What is machine learning?&lt;/h3&gt;&lt;p&gt;Machine learning is an artificial intelligence study field which deals with computer learning, especially with questions and answers on how computers can learn without being explicitly programmed.&lt;/p&gt;&lt;p&gt;Nowadays, many apps utilize machine learning to implement certain features, for example for image recognition, natural language processing and more.&lt;/p&gt;&lt;p&gt;To integrate machine learning into an application, basically two steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;strong&gt;Training&lt;/strong&gt;: Training a machine learning model involves choosing a learning algorithm and providing the model with labeled training data to learn from.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Inference&lt;/strong&gt;: Once the model was trained, we can ask it to make predictions about new data. This process is called inference.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that for iOS applications.&lt;/p&gt;&lt;h3&gt;Machine learning models in iOS&lt;/h3&gt;&lt;p&gt;In iOS, a machine learning model is represented by a &lt;code&gt;.mlmodel&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Apple provides several pretrained open source models which are ready to use, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;MNIST&lt;/code&gt; to classify a single handwritten digit&lt;/li&gt;&lt;li&gt;&lt;code&gt;MobileNetV2&lt;/code&gt;, &lt;code&gt;Resnet50&lt;/code&gt; or &lt;code&gt;SqueezeNet&lt;/code&gt; for image classification&lt;/li&gt;&lt;li&gt;&lt;code&gt;PoseNet&lt;/code&gt; for estimatations about joint positions for each person in an image&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A full list of pretrained models can be found &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/machine-learning/models/&quot;&gt;at the official Apple developer documentation&lt;/a&gt;. For each model, we can download a &lt;code&gt;.mlmodel&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;We can also use a custom trained model, for example we can build and train a model with the &lt;em&gt;Create ML&lt;/em&gt; app bundled with Xcode. Check out this article on &lt;a target=&quot;_blank&quot; href=&quot;/20230403-train-ml-model-with-create-ml-ios/&quot;&gt;how to train a model with Create ML&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;Integrating ML models into iOS projects&lt;/h3&gt;&lt;p&gt;We can add the model to an iOS project by simply dragging the &lt;code&gt;.mlmodel&lt;/code&gt; file into the project. When we open the file, we can see details like the input and output of the model.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/854d0f910f16473cb6a5caa577013953/2b78e/20230327-mlmodel-file.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:57.06666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABsklEQVQoz11TSY7cMAz0T9rWalnyova+dA8QJJe55Jqc8v9XVEDK6XHnQBAg5WKxis7GoUN8/sDHzz94fP7C/vkbzfRE6RzKyqPuOrTxjqbtUIWaa+4a3qea99zPtm/fsR5PTOuOOM5QtoLQFoVUEEqjuITQBlIbzm+hDGI/wNctsmHd0A0DmhjRdBHKlvBNi/s4IvY916yrcCsEciGRF4KHSWPf4j5OCE2HjB7d6KHUuEkF4yoM84L98cQ4zfAhQGsJrTWk0tDGIheCQa4sE4ZCpkvHrGgVyhRV3aDrB9aOsokPVF1ifB9G1ozXvzCk76iWMZixcKFm7ahYaANLQocapgrIw4y6X+B8gHGeewmofAHmSvMwBiTBp2VlR41zCZSMkIpDaYXtOLBsO1wIpxFkUjKKMGjldTu+AGm9Zd3YLVqpEBJCJUBiTiZN88Is6FxIe0E9GngCzuuWAGkKmUErUljWSL+dSN1FhKbl3r8tpDEv/RLgjkwZA3E69rq5i+DizNvxYPbWOR5USPklyQm40MrC+lOP/4/11Ojs0V9ADH3dQJl0+IVSLAsNoJNpY4+/ypVGe3y+CD0AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;.mlmodel file opened in Xcode&quot; title=&quot;.mlmodel file opened in Xcode&quot; src=&quot;/static/854d0f910f16473cb6a5caa577013953/2e9ed/20230327-mlmodel-file.png&quot; srcSet=&quot;/static/854d0f910f16473cb6a5caa577013953/12d18/20230327-mlmodel-file.png 375w,/static/854d0f910f16473cb6a5caa577013953/44507/20230327-mlmodel-file.png 750w,/static/854d0f910f16473cb6a5caa577013953/2e9ed/20230327-mlmodel-file.png 1500w,/static/854d0f910f16473cb6a5caa577013953/2b78e/20230327-mlmodel-file.png 2164w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Example of an .mlmodel file&lt;/div&gt;&lt;p&gt;In the example above, we see the &lt;code&gt;MobileNetV2&lt;/code&gt; model which was trained to classify the dominant object in an image.&lt;/p&gt;&lt;p&gt;The input of the model is an image and as output, we get a &lt;code&gt;classLabelProbs&lt;/code&gt; dictionary with probabilities of each category and a &lt;code&gt;classLabel&lt;/code&gt; which represents the most likely category.&lt;/p&gt;&lt;h3&gt;Utilizing the ML model to get predictions&lt;/h3&gt;&lt;p&gt;After dragging the model into the project, Xcode automatically generates a programmatic interface which we can use to interact with the model in our code. The code to get a prediction about an image could look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func classifyImage(_ image: UIImage) throws -&amp;gt; String? {
    let model = try MobileNetV2Int8LUT(configuration: MLModelConfiguration())
    guard let pixelBuffer = image.pixelBuffer() else { return nil }
    let prediction = try model.prediction(image: pixelBuffer)
    return prediction.classLabel
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, all we need to do is to initialize the model with a configuration and after that, we can use its &lt;code&gt;prediction(image:)&lt;/code&gt; function for inference.&lt;/p&gt;&lt;h3&gt;Further reading&lt;/h3&gt;&lt;p&gt;Additionally to Core ML, Apple provides domain-specific frameworks which use Core ML under the hood:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;em&gt;Vision&lt;/em&gt; for analyzing images&lt;/li&gt;&lt;li&gt;&lt;em&gt;Natural Language&lt;/em&gt; for processing text&lt;/li&gt;&lt;li&gt;&lt;em&gt;Speech&lt;/em&gt; for converting audio to text&lt;/li&gt;&lt;li&gt;&lt;em&gt;Sound Analysis&lt;/em&gt; for identifying sounds in audio&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These frameworks are more specialized and provide solutions for domain specific machine learning tasks which we can use our of the box.&lt;/p&gt;&lt;p&gt;For example, since Vision is specialized for images, it allows us to directly work with &lt;code&gt;UIImage&lt;/code&gt; objects without needing to convert them to a pixel buffer first.&lt;/p&gt;</content:encoded><category>machine learning</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick tip on how to unit test Swift optionals with XCTUnwrap]]></title><description><![CDATA[Learn how to shorten if let and guard statements when unit testing.]]></description><link>https://tanaschita.com/20230306-xctunwrap-quick-tip</link><guid isPermaLink="false">https://tanaschita.com/20230306-xctunwrap-quick-tip</guid><pubDate>Sun, 05 Mar 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When unit testing optionals in Swift, the first idea on how to do that might be to use &lt;code&gt;if let&lt;/code&gt; or &lt;code&gt;guard&lt;/code&gt; statements combined with the &lt;code&gt;XCTFail&lt;/code&gt; assertion method. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func testMixingSmoothies() throws {
  let smoothieMaker = SmoothieMaker()
  guard let bananaSmoothie = smoothieMaker.makeBananaSmoothie() else {
    XCTFail(&amp;quot;bananaSmoothie should not be nil&amp;quot;)
    return
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The XCTest framework actually provides a more elegant solution.&lt;/p&gt;&lt;p&gt;It provides the &lt;code&gt;XCTUnwrap&lt;/code&gt; method which attempts to unwrap the optional and throws an error i.e. the test fails if the optional is nil.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func testMixingSmoothies() throws {
  let smoothieMaker = SmoothieMaker()
  let bananaSmoothie = try XCTUnwrap(smoothieMaker.makeBananaSmoothie())
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This approach eliminates the need to write repeating &lt;code&gt;XCTFail&lt;/code&gt; descriptions. All we need to do is to mark our test function with &lt;code&gt;throws&lt;/code&gt;.&lt;/p&gt;</content:encoded><category>testing</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick guide on fastlane for iOS development]]></title><description><![CDATA[Learn to automate steps like running tests, code signing, build uploading and more.]]></description><link>https://tanaschita.com/20230213-fastlane-ios-developer-guide</link><guid isPermaLink="false">https://tanaschita.com/20230213-fastlane-ios-developer-guide</guid><pubDate>Sun, 12 Feb 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When it comes to publishing an iOS app to the App Store, we need to perform multiple steps like running tests, code signing, archiving and uploading the build, creating screenshots, setting metadata and more. These steps can be very time consuming, especially when publishing on a regular basis.&lt;/p&gt;&lt;p&gt;fastlane is an open source tool that allows you to automate all of these steps and more. At its core, fastlane is a set of Ruby scripts that wrap xcodebuild, the App Store Connect API and more and allow us iOS developers to use commands to automate iOS builds.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s see how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Installing fastlane&lt;/h3&gt;&lt;p&gt;The recommended way to install fastlane for iOS projects is by using Bundler and running &lt;code&gt;bundle init&lt;/code&gt; in the root directory of the project. This creates a &lt;code&gt;Gemfile&lt;/code&gt; where we can add the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;source &amp;quot;https://rubygems.org&amp;quot;

gem &amp;quot;fastlane&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After running &lt;code&gt;bundle install&lt;/code&gt;, Bundler creates a &lt;code&gt;Gemfile.lock&lt;/code&gt; file which clearly defines the fastlane version to be used and its dependencies for the specific iOS project.&lt;/p&gt;&lt;p&gt;To create an initial fastlane setup, we can now run &lt;code&gt;bundle exec fastlane init&lt;/code&gt; which creates a &lt;code&gt;fastlane&lt;/code&gt; folder including the files &lt;code&gt;Appfile&lt;/code&gt; and &lt;code&gt;Fastfile&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Defining and running lanes&lt;/h3&gt;&lt;p&gt;From the created files, the most interesting one is &lt;code&gt;fastlane/Fastfile&lt;/code&gt; which is a Ruby configuration file we can use to add so called lanes for signing, testing, building, deploying etc.&lt;/p&gt;&lt;p&gt;Each lane is a group of actions. A lane definition looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;lane :my_lane do
  # fastlane actions
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which we can run with the following command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;bundle exec fastlane [lane]
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Using fastlane actions&lt;/h3&gt;&lt;p&gt;fastlane provides predefined actions for different purposes like running tests, taking screenshots, deploying the app etc. We can combine multiple actions to build a lane, for example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;lane :beta do
  sync_code_signing(type: &amp;quot;appstore&amp;quot;)
  build_app(scheme: &amp;quot;ExampleApp&amp;quot;)
  upload_to_testflight
  slack(message: &amp;quot;New beta build available.&amp;quot;)
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;#x27;ll look at some available actions in more detail later in this article.&lt;/p&gt;&lt;h3&gt;Authenticating with Apple services&lt;/h3&gt;&lt;p&gt;Several fastlane actions communicate with Apple services that need authentication.&lt;/p&gt;&lt;p&gt;fastlane has historically used Apple IDs with username and password for authentication. Since the announcement of the App Store Connect API at WWDC18, the official and recommended way for authentication is by using the App Store Connect API key. Checkout this guide to learn more on &lt;a target=&quot;_blank&quot; href=&quot;/20221226-authorizing-app-store-connect-api-requests/&quot;&gt;how to generate an API key&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;fastlane provides the &lt;code&gt;app_store_connect_api_key&lt;/code&gt; action which we can use to generate the key we need for JWT authorization:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;api_key = app_store_connect_api_key(
    key_id: &amp;quot;79NWH6F376&amp;quot;,
    issuer_id: &amp;quot;69a6de73-ea56-47e3-e053-5b8c7c11a4d1&amp;quot;,
    key_filepath: &amp;quot;./AuthKey_79NWH6F376.p8&amp;quot;
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can then use the &lt;code&gt;api_key&lt;/code&gt; to pass in as a parameter into fastlane actions, for example &lt;code&gt;pilot(api_key: api_key)&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Sync certificates and provisioning profiles across the team&lt;/h3&gt;&lt;p&gt;For many fastlane actions like deploying to the App Store, a beta testing service or installing the app on a device, a valid certificate and provisioning profile is required.&lt;/p&gt;&lt;p&gt;fastlane provides an action called &lt;code&gt;match&lt;/code&gt; which creates all required certificates &amp;amp; provisioning profiles and stores them in a separate Git repository.&lt;/p&gt;&lt;p&gt;Running &lt;code&gt;fastlane match init&lt;/code&gt; will create a &lt;code&gt;Matchfile&lt;/code&gt; which we can fill out with required properties like Git URL, app identifier etc.:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;git_url(&amp;quot;https://github.com/tanaschita/exampleAppCertificates&amp;quot;)
app_identifier(&amp;quot;com.tanaschita.exampleapp&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After doing that, we can run &lt;code&gt;fastlane match appstore&lt;/code&gt; or &lt;code&gt;fastlane match development&lt;/code&gt; to generate provisioning profiles and certificates and store them in the configured storage.&lt;/p&gt;&lt;p&gt;After successfully setting that up, we can use the match action on our &lt;code&gt;Fastfile&lt;/code&gt; to automatically fetch the latest provisioning profiles and certificates:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;match(api_key: api_key, type: &amp;quot;appstore&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Running tests&lt;/h3&gt;&lt;p&gt;Now, let&amp;#x27;s look at some actions fastlane provides.&lt;/p&gt;&lt;p&gt;For example, to run tests, we can use fastlane&amp;#x27;s &lt;code&gt;run_tests&lt;/code&gt; action as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;lane :tests do
  run_tests(workspace: &amp;quot;ExampleApp.xcworkspace&amp;quot;,
            devices: [&amp;quot;iPhone 13 Pro&amp;quot;],
            scheme: &amp;quot;ExampleAppTests&amp;quot;)
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are various other parameters we can provide such as &lt;code&gt;reset_simulator&lt;/code&gt; to automatically erase the simulator before running the iOS application or &lt;code&gt;slack_url&lt;/code&gt; to posts results in Slack. To see a full list of parameters, run &lt;code&gt;fastlane action run_tests&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Taking screenshots and uploading screenshots to App Store Connect&lt;/h3&gt;&lt;p&gt;To automate the process of taking screenshots with fastlane, we can use its &lt;code&gt;snapshot&lt;/code&gt; action which uses the capabilities of UI tests to create the desired application state.&lt;/p&gt;&lt;p&gt;Check out this &lt;a target=&quot;_blank&quot; href=&quot;/20230724-fastlane-screenshots-ios/&quot;&gt;step-by-step guide on how to setup fastlane&amp;#x27;s snapshot action&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After creating screenshots, we can use &lt;code&gt;fastlane deliver&lt;/code&gt; to upload them to App Store Connect.&lt;/p&gt;&lt;h3&gt;Building the application&lt;/h3&gt;&lt;p&gt;To build the iOS application, we can use fastlane&amp;#x27;s &lt;code&gt;build_app&lt;/code&gt; action:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;build_app(scheme: &amp;quot;ExampleApp&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It creates a &lt;code&gt;ExampleApp.ipa&lt;/code&gt; in the current directory.&lt;/p&gt;&lt;h3&gt;Uploading to Testflight&lt;/h3&gt;&lt;p&gt;Since Testflight is part of App Store Connect, uploading a beta build is just another fastlane action:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;upload_to_testflight
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;fastlane also supports beta deployments to Firebase, HockeyApp and TestFairy.&lt;/p&gt;&lt;h3&gt;Uploading to the App Store&lt;/h3&gt;&lt;p&gt;Uploading the build to the App Store is also just another fastlane action:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;upload_to_app_store
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;I hope, this guide helped you to get an overview on fastlane which is a powerful tool for automating the development and deployment process for iOS apps. To dive deeper into fastlane, check out their &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://docs.fastlane.tools&quot;&gt;official documentation&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>continuous integration</category><category>ios</category></item><item><title><![CDATA[How to import Objective-C types as optionals or non-optionals into Swift]]></title><description><![CDATA[Learn how to use nullability declarations and macros.]]></description><link>https://tanaschita.com/20230130-nullability-declarations-objective-c</link><guid isPermaLink="false">https://tanaschita.com/20230130-nullability-declarations-objective-c</guid><pubDate>Sun, 29 Jan 2023 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working on a project that consists of a mixed code base, we might need to import and use Objective-C code in Swift files. Since every object in Objective-C can become nil, Swift imports every type as an implicitly unwrapped optional without any nullability annotations.&lt;/p&gt;&lt;p&gt;To be more expressive in Swift by using either optional or non-optional types, we have the possibility to extend the Objective-C code with nullability declarations and macros.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Nullability declarations&lt;/h3&gt;&lt;p&gt;We can annotate Objective-C code to indicate whether an instance can have a &lt;code&gt;nil&lt;/code&gt; value. Swift uses those annotations to import declarations either as optional or non-optional:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;types with a &lt;code&gt;nonnullable&lt;/code&gt; annotation are imported as non-optionals&lt;/li&gt;&lt;li&gt;types with a &lt;code&gt;nullable&lt;/code&gt; annotation are imported as optionals&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;- (nullable Tea *)search:(nonnull NSString *)name;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The example above is imported to Swift as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;search(name: String) -&amp;gt; Tea?
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Without a nullability annotation, Swift would import a type as an implicitly unwrapped optional, which would look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;search(name: String!) -&amp;gt; Tea!
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Nullability macros&lt;/h3&gt;&lt;p&gt;Additionally, we can use the macros &lt;code&gt;NS_ASSUME_NONNULL_BEGIN&lt;/code&gt; and &lt;code&gt;NS_ASSUME_NONNULL_END&lt;/code&gt; to annotate regions in Objective-C as &lt;code&gt;nonnullable&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;This can simplify the process of annotating Objective-C code if we&amp;#x27;d like to import most of the types as non-optionals into Swift.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-objectivec&quot;&gt;NS_ASSUME_NONNULL_BEGIN

@interface SomeClass : NSObject

- (nullable String *)subtitleForItem:(SomeItem *)item;
@property NSString *title;
@property NSArray&amp;lt;SomeItem *&amp;gt; *items;
@end

NS_ASSUME_NONNULL_END
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, all types will be imported as non-optionals into Swift, except those we annotated with a nullable declaration.&lt;/p&gt;</content:encoded><category>objective-c</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Authorizing App Store Connect API requests]]></title><description><![CDATA[Learn how to create and use a JSON Web Token when communicating with the App Store Connect API.]]></description><link>https://tanaschita.com/20221226-authorizing-app-store-connect-api-requests</link><guid isPermaLink="false">https://tanaschita.com/20221226-authorizing-app-store-connect-api-requests</guid><pubDate>Sun, 25 Dec 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As mentioned in this &lt;a href=&quot;/20221128-app-store-connect-api-quide/&quot;&gt;developer guide on the App Store Connect API&lt;/a&gt;, a &lt;strong&gt;JSON Web Token (JWT)&lt;/strong&gt; is needed to authorize each request we make.&lt;/p&gt;&lt;p&gt;The JWT is an open standard that defines a way to securely transmit information. To create a JWT, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Generate and download an &lt;strong&gt;API key&lt;/strong&gt; from the &lt;code&gt;Users and Access&lt;/code&gt; section in App Store Connect.&lt;/li&gt;&lt;li&gt;Create a &lt;strong&gt;JWT header and payload&lt;/strong&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Sign the JWT with the private key&lt;/strong&gt; of the API key.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Include the JWT as a bearer token&lt;/strong&gt; in the request&amp;#x27;s authorization header.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at each step in more detail.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;1. Generating an API key&lt;/h3&gt;&lt;p&gt;An API key can be created in the &lt;code&gt;Users and Access&lt;/code&gt; section in App Store Connect. When generating a key, we select a user role which determines what type of information this key can access. The roles are the same we already know from roles we can assign to team members.&lt;/p&gt;&lt;p&gt;An API key is a pair consisting of a public and a private key. After generating a key, we can download the private key as a &lt;code&gt;.p8&lt;/code&gt; file, while Apple keeps the public key.&lt;/p&gt;&lt;p&gt;For security reasons, Apple doesn&amp;#x27;t store the private key, so we can only download it once. From here, we can also revoke the API key immediately if it becomes inactive, lost or compromised.&lt;/p&gt;&lt;h3&gt;2. Creating a JWT header and payload&lt;/h3&gt;&lt;p&gt;A JWT header and payload might look as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;header&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
    &amp;quot;alg&amp;quot;: &amp;quot;ES256&amp;quot;,
    &amp;quot;kid&amp;quot;: &amp;quot;79NWH6F376&amp;quot;,
    &amp;quot;typ&amp;quot;: &amp;quot;JWT&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;payload&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
    &amp;quot;iss&amp;quot;: &amp;quot;69a6de73-ea56-47e3-e053-5b8c7c11a4d1&amp;quot;,
    &amp;quot;iat&amp;quot;: 1528407600,
    &amp;quot;exp&amp;quot;: 1528408800,
    &amp;quot;aud&amp;quot;: &amp;quot;appstoreconnect-v1&amp;quot;,
    &amp;quot;scope&amp;quot; : [&amp;quot;PATCH /v1/apps&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Whereas&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;kid&lt;/code&gt; is the &lt;code&gt;KEY ID&lt;/code&gt; of the API key we can copy from App Store Connect.&lt;/li&gt;&lt;li&gt;&lt;code&gt;iss&lt;/code&gt; is the issuer id we can also copy from the API key entry in App Store Connect.&lt;/li&gt;&lt;li&gt;&lt;code&gt;iat&lt;/code&gt; is the token&amp;#x27;s creation time in UNIX epoch time&lt;/li&gt;&lt;li&gt;&lt;code&gt;exp&lt;/code&gt; is the token&amp;#x27;s expiration time in UNIX epoch time&lt;/li&gt;&lt;li&gt;&lt;code&gt;scope&lt;/code&gt; specifies which API requests should be accepted for the token&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;3. Signing the JWT token&lt;/h3&gt;&lt;p&gt;There are a variety of open source libraries available online for creating and signing JWT tokens. See JWT.io for more information.&lt;/p&gt;&lt;h3&gt;4. Include JWT in the request&amp;#x27;s authorization header&lt;/h3&gt;&lt;p&gt;We now can use the signed token to send requests to the App Store Connect API. For example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;curl -v -H &amp;#x27;Authorization: Bearer [signed token]&amp;#x27;
&amp;quot;https://api.appstoreconnect.apple.com/v1/apps&amp;quot;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>continuous integration</category><category>automation</category><category>ios</category></item><item><title><![CDATA[How to perform a lightweight migration in Core Data]]></title><description><![CDATA[Learn how Core Data can automatically migrate compatible model changes.]]></description><link>https://tanaschita.com/core-data-lightweight-migration</link><guid isPermaLink="false">https://tanaschita.com/core-data-lightweight-migration</guid><pubDate>Sun, 18 Dec 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A database migration is required whenever we need to make changes to our Core Data model. For a specific set of changes, Core Data can perform an almost automatic migration, called &lt;strong&gt;lightweight migration&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;This allows us to evolve our schema without losing existing data or manually defining mapping rules.&lt;/p&gt;&lt;p&gt;Examples of supported changes include adding, renaming, or deleting entities, attributes, or relationships, and changing relationship types.&lt;/p&gt;&lt;p&gt;When our model changes exceed what Core Data can infer, we need to perform a &lt;strong&gt;heavyweight (manual)&lt;/strong&gt; migration.&lt;/p&gt;&lt;p&gt;In this article, we’ll look at how to perform a lightweight migration in Core Data.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Steps to perform a lightweight migration in Core Data&lt;/h3&gt;&lt;p&gt;To perform a lightweight migration in Core Data, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Enabling lightweight migration&lt;/li&gt;&lt;li&gt;Creating a new model version&lt;/li&gt;&lt;li&gt;Modifying the new model&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at these steps in more detail.&lt;/p&gt;&lt;h3&gt;1. Enabling lightweight migration&lt;/h3&gt;&lt;p&gt;When we use the &lt;code&gt;NSPersistentContainer&lt;/code&gt; class to create and manage the Core Data stack, we don&amp;#x27;t need to do any additional setup work, the lightweight migration is automatically activated for us.&lt;/p&gt;&lt;p&gt;If we build our own Core Data stack manually, we can enable these options explicitly when adding our store:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let options = [
    NSMigratePersistentStoresAutomaticallyOption: true,
    NSInferMappingModelAutomaticallyOption: true
]
do {
    try coordinator.addPersistentStore(
        ofType: NSSQLiteStoreType,
        configurationName: nil,
        at: url,
        options: options
    )
} catch {
    // handle migration failure
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These two options tell Core Data to automatically infer a mapping model and migrate data when it detects a compatible schema change.&lt;/p&gt;&lt;h3&gt;2. Creating a new model version&lt;/h3&gt;&lt;p&gt;To perform an automatic migration, Core Data needs both the source and destination versions of our data model.&lt;/p&gt;&lt;p&gt;If we modify an existing &lt;code&gt;.xcdatamodeld&lt;/code&gt; directly after shipping our app, Core Data won’t know how to migrate and will throw an incompatible model error.&lt;/p&gt;&lt;p&gt;To avoid that, we create a new model version by selecting &lt;code&gt;Editor &amp;gt; Add Model Version&lt;/code&gt; in Xcode&amp;#x27;s menu. This will add a new &lt;code&gt;.xcdatamodel&lt;/code&gt; file to our project.&lt;/p&gt;&lt;p&gt;After adding a new version, we can set it to the current version in Xcode&amp;#x27;s File Inspector. Now we can safely make changes without breaking existing stores.&lt;/p&gt;&lt;h3&gt;3. Modifying the new data model version&lt;/h3&gt;&lt;p&gt;We can now make changes to our model. For Core Data to be able to create an inferred mapping model, the changes must fit a specific pattern, for example:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Adding or deleting entities, attributes or relationships.&lt;/li&gt;&lt;li&gt;Renaming entities, attributes or relationships by setting the renaming identifier to the name of the corresponding property or entity in the previous model in Xcode&amp;#x27;s Data Model inspector.&lt;/li&gt;&lt;li&gt;Changing required attributes to optional attributes or vice versa with a default value.&lt;/li&gt;&lt;li&gt;Changing a relationship from to-one to to-many, or a nonordered to an ordered relationship.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To verify whether Core Data can infer a mapping between two model versions, we can use:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let inferred = try? NSMappingModel.inferredMappingModel(
    forSourceModel: oldModel,
    destinationModel: newModel
)

if inferred != nil {
    // Lightweight migration is possible
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If this call returns nil, Core Data cannot infer the mapping, meaning we&amp;#x27;ll need a heavier, manual migration.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Developer guide on the App Store Connect API]]></title><description><![CDATA[Learn how to automate App Store Connect.]]></description><link>https://tanaschita.com/20221128-app-store-connect-api-quide</link><guid isPermaLink="false">https://tanaschita.com/20221128-app-store-connect-api-quide</guid><pubDate>Sun, 27 Nov 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The App Store Connect API allows us to automate tasks that usually can be performed through the Apple Developer website and App Store Connect.&lt;/p&gt;&lt;p&gt;It&amp;#x27;s a REST API that enables us to automate tasks like managing provisioning, beta builds, in-app purchases, app metadata and more. Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;App Store Connect API overview&lt;/h3&gt;&lt;p&gt;The App Store Connect API provides a single unified REST resource model, which means that the same patterns are applied throughout the whole API.&lt;/p&gt;&lt;p&gt;To construct an App Store Connect API URL, we can always apply the following pattern:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{base url}/{version number}/{resource type name}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;whereas:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;base URL is &lt;code&gt;https://api.appstoreconnect.apple.com&lt;/code&gt;&lt;/li&gt;&lt;li&gt;current version number is &lt;code&gt;v1&lt;/code&gt;&lt;/li&gt;&lt;li&gt;some example resources are:&lt;ul&gt;&lt;li&gt;&lt;code&gt;apps&lt;/code&gt; to manage and maintain the existing apps&lt;/li&gt;&lt;li&gt;&lt;code&gt;builds&lt;/code&gt; to manage existing builds&lt;/li&gt;&lt;li&gt;&lt;code&gt;bundleIds&lt;/code&gt; to manage the app&amp;#x27;s unique identifier&lt;/li&gt;&lt;li&gt;&lt;code&gt;devices&lt;/code&gt; to manage devices used for development and testing&lt;/li&gt;&lt;li&gt;&lt;code&gt;users&lt;/code&gt; to manage users&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The API provides standard REST methods:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;GET&lt;/code&gt; to get a resource or a list of resources&lt;/li&gt;&lt;li&gt;&lt;code&gt;POST&lt;/code&gt; to create a resource&lt;/li&gt;&lt;li&gt;&lt;code&gt;PATCH&lt;/code&gt; to change a resource&lt;/li&gt;&lt;li&gt;&lt;code&gt;DELETE&lt;/code&gt; to delete a resource&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s look at some examples.&lt;/p&gt;&lt;h3&gt;GET requests&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s start with a GET request. Understanding the example below will allow us to apply the knowledge to all other requests of the API.&lt;/p&gt;&lt;p&gt;For example, to list all apps available in App Store Connect, we could send the following request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET https://api.appstoreconnect.apple.com/v1/apps
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The returned JSON follows a pattern. A successful response always contains a &lt;code&gt;data&lt;/code&gt; property. In our case, it contains an array of app objects:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
    &amp;quot;data&amp;quot;: [
        {
            &amp;quot;attributes&amp;quot;:
            {
                &amp;quot;bundleId&amp;quot;: &amp;quot;tanaschita.com.exampleApp&amp;quot;,
                &amp;quot;name&amp;quot;: &amp;quot;ExampleApp&amp;quot;,
                &amp;quot;primaryLocale&amp;quot;: &amp;quot;en&amp;quot;,
                ...
            },
            &amp;quot;id&amp;quot;: &amp;quot;16fksv98-3j6d-39ok-12dh&amp;quot;,
            &amp;quot;relationships&amp;quot;: []
            ...
        }
    ]
    ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each resource has an &lt;code&gt;attributes&lt;/code&gt;, an &lt;code&gt;id&lt;/code&gt; and a &lt;code&gt;relationships&lt;/code&gt; property.&lt;/p&gt;&lt;p&gt;In case of an app object, the &lt;code&gt;attributes&lt;/code&gt; property contains values like the app&amp;#x27;s bundle ID, name, primary locale etc.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;relationships&lt;/code&gt; property contains references to different parts of the API which we&amp;#x27;ll discuss closely later on.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; value is a unique id, which identifies the resource and which we can use to form other requests, for example to fetch a specific resource.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET https://api.appstoreconnect.apple.com/v1/{resourceName}/{resourceId}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In case of our example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET https://api.appstoreconnect.apple.com/v1/apps/16fksv98-3j6d-39ok-12dh
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;POST, PATCH and DELETE requests&lt;/h3&gt;&lt;p&gt;Just like GET requests, POST, PATCH and DELETE requests also follow a pattern. As an example, we&amp;#x27;ll use the &lt;code&gt;users&lt;/code&gt; resource which allows us to change or delete users.&lt;/p&gt;&lt;p&gt;Just like in App Store Connect itself, we have to invite the user to our team and they can accept that invitation. To do that, the API provides the &lt;code&gt;userInvitations&lt;/code&gt; resource.&lt;/p&gt;&lt;p&gt;A request might look like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;POST /v1/userInvitations

{
    &amp;quot;data&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;userInvitations&amp;quot;,
        &amp;quot;attributes&amp;quot;: {
            &amp;quot;firstName&amp;quot;: &amp;quot;Natascha&amp;quot;,
            &amp;quot;lastName&amp;quot;: &amp;quot;Fadeeva&amp;quot;,
            &amp;quot;email&amp;quot;: &amp;quot;tanaschita@gmail.com&amp;quot;,
            &amp;quot;roles&amp;quot;: [&amp;quot;DEVELOPER&amp;quot;],
            &amp;quot;allAppsVisible&amp;quot;: true

        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, the user object follows the same principles we already saw in the app object.&lt;/p&gt;&lt;p&gt;As a JSON response to our POST request, we get the same object enriched with some values like the user&amp;#x27;s &lt;code&gt;id&lt;/code&gt;, which we can then use to modify or delete a user.&lt;/p&gt;&lt;p&gt;To modify a user, we can send a &lt;code&gt;PATCH&lt;/code&gt; request including the attributes we&amp;#x27;d like to change. For example, to change the roles of a user, we could send the following request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;PATCH /v1/users/userId

{
    &amp;quot;data&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;users&amp;quot;,
        &amp;quot;attributes&amp;quot;: {
            &amp;quot;roles&amp;quot;: [&amp;quot;ADMIN&amp;quot;],
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To delete a user, we can just do the following:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;DELETE /v1/users/userId
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Relationships&lt;/h3&gt;&lt;p&gt;To understand the &lt;code&gt;relationships&lt;/code&gt; property we already saw earlier, let&amp;#x27;s look at the &lt;code&gt;betaTesters&lt;/code&gt; and the &lt;code&gt;betaGroups&lt;/code&gt; resource and how they relate to each other.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;betaTesters&lt;/code&gt; resource represents users who can install and test prerelease builds. The &lt;code&gt;betaGroups&lt;/code&gt; resource represents a group of testers who can access builds for testing.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;relationships&lt;/code&gt; object of a group looks as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;{
    &amp;quot;apps&amp;quot;: ...,
    &amp;quot;betaTesters&amp;quot;: {
        &amp;quot;links&amp;quot;: {
            &amp;quot;related&amp;quot;: &amp;quot;/v1/betaGroups/16fksv98-3j6d-39ok-12dj/betaTesters&amp;quot;,
            &amp;quot;self&amp;quot;: &amp;quot;/v1/betaGroups/16fksv98-3j6d-39ok-12dj/relationships/betaTesters&amp;quot;
        },
        ...
    },
    builds: ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, a beta tester has 3 relationships: &lt;code&gt;apps&lt;/code&gt;, &lt;code&gt;betaGroups&lt;/code&gt; and &lt;code&gt;builds&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Each relationship again follows the same pattern, for example providing a &lt;code&gt;links&lt;/code&gt; property. Each &lt;code&gt;links&lt;/code&gt; object consists of the following two links:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;related&lt;/code&gt;: a url which we can use to get all testers from this group&lt;/li&gt;&lt;li&gt;&lt;code&gt;self&lt;/code&gt;: a url which represents the relationship between a beta tester and and a beta group&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To connect testers and groups, we modify the relationship. For example, to add a user to a group, we would use the &lt;code&gt;self&lt;/code&gt; link with the following request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;POST /v1/betaGroups/16fksv98-3j6d-39ok-12dj/relationships/betaTesters

{
    &amp;quot;data&amp;quot;: [
        {
            &amp;quot;type&amp;quot;: &amp;quot;betaTesters&amp;quot;,
            &amp;quot;id&amp;quot;: &amp;quot;19fksv98-3j6d-39ok-12dj&amp;quot;
        },
        ...
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To delete the tester, we just use the same request as above with a &lt;code&gt;DELETE&lt;/code&gt; method instead of a &lt;code&gt;POST&lt;/code&gt; method.&lt;/p&gt;&lt;h3&gt;Query parameters&lt;/h3&gt;&lt;p&gt;Each API endpoint provides query parameters we can use for example to filter, limit, sort or specify the results we need. Let&amp;#x27;s look at the following request:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;GET /v1/betaGroups?include=betaTesters&amp;amp;fields[betaTesters]=email
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The request sends two query parameters:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;include=betaTesters&lt;/code&gt; which will include testers for every group in the response&lt;/li&gt;&lt;li&gt;&lt;code&gt;fields[betaTesters]=email&lt;/code&gt; which defines the fields that should be returned for the testers&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Authorizing API requests&lt;/h3&gt;&lt;p&gt;All App Store Connect API requests require authorization. The API expects a &lt;strong&gt;JSON Web Token (JWT)&lt;/strong&gt; to authorize each request we make. The JWT is an open standard that defines a way to securely transmit information. Checkout this &lt;a href=&quot;/20221226-authorizing-app-store-connect-api-requests/&quot;&gt;guide on how to authorize App Store Connect API requests&lt;/a&gt; to learn more.&lt;/p&gt;&lt;p&gt;All available API endpoints are documented at &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://developer.apple.com/documentation/appstoreconnectapi/&quot;&gt;Apple&amp;#x27;s official App Store Connect API documentation&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>continuous integration</category><category>automation</category><category>ios</category></item><item><title><![CDATA[How to handle non-optional Core Data properties in Swift]]></title><description><![CDATA[Explore the possibilities to use non-optional Swift values in Core Data.]]></description><link>https://tanaschita.com/20221114-how-to-handle-non-optional-core-data-values-in-swift</link><guid isPermaLink="false">https://tanaschita.com/20221114-how-to-handle-non-optional-core-data-values-in-swift</guid><pubDate>Sun, 06 Nov 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As described in this article on &lt;a href=&quot;/20221024-how-to-create-a-managed-object-subclass-xcode-core-data/&quot;&gt;how to create an NSManagedObject subsclass for a Core Data entity in Xcode&lt;/a&gt;, we can either let Xcode automatically generate entity subclasses and their properties for us or create and manage them manually.&lt;/p&gt;&lt;p&gt;While the auto code generation has the advantage that the entity classes are always up-to-date, the manual option gives us more control and flexibility for example to improve the handling of non-optional properties.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how to do that.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Declaring a property as optional or non-optional in Core Data&lt;/h3&gt;&lt;p&gt;When adding properties in Core Data&amp;#x27;s &lt;code&gt;.xcdatamodeld&lt;/code&gt; file, we can decide whether the property should be optional or non-optional:&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5e6ab6f510ef5d15f29f5b81a71dd140/a381e/20221114-non-optional-properties-core-data.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:18.933333333333337%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA/ElEQVQY013OzUrDQBiF4VyDP+BCSDKZNDOTmWl+J0mT2tTaCqII4gV4DRYEcSVehfdavlPMQsHFA+/qcLzaNQc7zw4Rn014nPz2fyHjpLXFuBppd7ND07RwTQfnWtQ/+mt4Slos+w3MPEfIIhibIRESLOKIePwnihGwGHng00Nu8dQ53GqJpiixXK2n4awe4Bk70P3LN+XPX3Tav1L6+En+9oNO+j1djG90Puzp7OqdLs2WZjEnJTVcVdNmXMPVDnY6oOAHDEJIeEJYdN0ddLGAPytgygEq7+EnBZiqEIgSoawQcIUkkdAmQ5aXcO0CReWQagupNIRMYbMCR8G8fc42eqcCAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Optional checkbox for a Core Data entity property&quot; title=&quot;Optional checkbox for a Core Data entity property&quot; src=&quot;/static/5e6ab6f510ef5d15f29f5b81a71dd140/2e9ed/20221114-non-optional-properties-core-data.png&quot; srcSet=&quot;/static/5e6ab6f510ef5d15f29f5b81a71dd140/12d18/20221114-non-optional-properties-core-data.png 375w,/static/5e6ab6f510ef5d15f29f5b81a71dd140/44507/20221114-non-optional-properties-core-data.png 750w,/static/5e6ab6f510ef5d15f29f5b81a71dd140/2e9ed/20221114-non-optional-properties-core-data.png 1500w,/static/5e6ab6f510ef5d15f29f5b81a71dd140/a381e/20221114-non-optional-properties-core-data.png 1574w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Optional checkbox for a Core Data entity property.&lt;/div&gt;&lt;p&gt;But after declaring a property as non-optional, Core Data still generates the username property as a Swift optional property:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@NSManaged public var username: String?
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;#x27;s because the concept of optional and non-optional values in Core Data is different from that in Swift.&lt;/p&gt;&lt;h3&gt;The concept behind optional and non-optional values in Core Data&lt;/h3&gt;&lt;p&gt;In Swift, the compiler enforces the presence or absence of a value, which gives us guarantees about whether or not it is safe to expect a value at compile time.&lt;/p&gt;&lt;p&gt;Just like in Swift, declaring a property as non-optional in Core Data means that we need to handle the abscence accordingly, but we don&amp;#x27;t get any compiler guarantees or enforcements when initializing the object. That&amp;#x27;s because when working with Core Data, the framework checks the values at run time with validation rules and throws an error when the validation fails.&lt;/p&gt;&lt;h3&gt;Using non-optional Swift values in Core Data&lt;/h3&gt;&lt;p&gt;Not having compiler guarantees for non-optionals when working with Core Data is a drawback. Let&amp;#x27;s look at how we can solve this.&lt;/p&gt;&lt;h4&gt;1. Declaring the properties as non-optional in Swift&lt;/h4&gt;&lt;p&gt;To define Core Data properties as Swift non-optionals, we need to manually generate the entity subclasses. After doing that, the first thing we can do is to simply declare the properties as non-optional by removing question marks where needed:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;public class User: NSManagedObject {

    @NSManaged public var username: String
    @NSManaged public var lastLogin: Date
    @NSManaged public var verified: Bool
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that in place, the compiler will prevent us from setting any of these properties to &lt;code&gt;nil&lt;/code&gt;. That&amp;#x27;s a good start.&lt;/p&gt;&lt;p&gt;But since the properties are marked with the &lt;code&gt;@NSManaged&lt;/code&gt; attribute - which indicates that Core Data dynamically provides its implementation at runtime - Swift’s initialization rules do not apply. This means that we can still call &lt;code&gt;init()&lt;/code&gt; and &lt;code&gt;init(context:)&lt;/code&gt; without initializing any of the properties.&lt;/p&gt;&lt;h4&gt;2. Preventing initializers from being called&lt;/h4&gt;&lt;p&gt;To prevent initializers from being called, we can use Swift&amp;#x27;s &lt;code&gt;@available&lt;/code&gt; attribute to mark the init methods as &lt;code&gt;unavailable&lt;/code&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@available(*, unavailable)
public init() {
    fatalError()
}

@available(*, unavailable)
public init(context: NSManagedObjectContext) {
    fatalError()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With that in place, the compiler will now produce an error when calling &lt;code&gt;init()&lt;/code&gt; or &lt;code&gt;init(context:)&lt;/code&gt;.&lt;/p&gt;&lt;h4&gt;3. Providing a designated initializer&lt;/h4&gt;&lt;p&gt;Now we can provide our own initializer:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;public init(context: NSManagedObjectContext,
            username: String,
            lastLogin: Date? = nil,
            verified: Bool = false) {
    let entity = NSEntityDescription.entity(forEntityName: &amp;quot;User&amp;quot;, in: context)!
    super.init(entity: entity, insertInto: context)
    self.username = username
    self.lastLogin = lastLogin
    self.verified = verified
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The initializer enforces non-optional values to be passed in with a value and allows us to define default values where needed.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The approach described above provides a lot more clarity and safety when working with entities in code.&lt;/p&gt;&lt;p&gt;A drawback is that there is a lot of manual work to do to enforce the rules Swift usually enforces for us. Every time we edit an entity, we need to manually keep the initializer and properties up-to-date.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[Understanding different cache policies when working with URLRequest in Swift]]></title><description><![CDATA[Learn how to manage cache when sending HTTP requests in your iOS applications.]]></description><link>https://tanaschita.com/20221031-managing-cache-when-working-with-urlsession-in-ios</link><guid isPermaLink="false">https://tanaschita.com/20221031-managing-cache-when-working-with-urlsession-in-ios</guid><pubDate>Sun, 30 Oct 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When in comes to server communication in iOS, the system provides different caching strategies to improve performance and reduce network traffic.&lt;/p&gt;&lt;p&gt;By choosing a cache policy, we can decide whether the caching should depend on expiration dates or disabled entirely or whether the server should be contacted to determine if the content has changed since the last request.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in and explore available &lt;code&gt;URLRequest&lt;/code&gt; cache policies and their consequences.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Setting a cache policy for URL requests&lt;/h3&gt;&lt;p&gt;When creating a &lt;code&gt;URLRequest&lt;/code&gt;, we can use its &lt;code&gt;cachePolicy&lt;/code&gt; property to specify which caching should be performed, for example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alternatively, we can set the desired caching policy when configuring a &lt;code&gt;URLSession&lt;/code&gt;. All requests created from this session inherit its cache policy.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad

let urlSession = URLSession(configuration: configuration)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The following caching policies are available:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.useProtocolCachePolicy&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.reloadIgnoringLocalCacheData&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.reloadIgnoringLocalAndRemoteCacheData&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.returnCacheDataElseLoad&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.returnCacheDataDontLoad&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;.reloadRevalidatingCacheData&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s go through each policy and see how the cache is being managed.&lt;/p&gt;&lt;h3&gt;.useProtocolCachePolicy&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.useProtocolCachePolicy&lt;/code&gt; value is the default cache policy. It uses the caching logic defined in the protocol implementation.&lt;/p&gt;&lt;p&gt;When working with HTTP or HTTPS, the caching capabilities are built into the protocol so the server is able to communicate with the client on how the content should be cached.&lt;/p&gt;&lt;p&gt;For example, the server might send the following cache control headers:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Last-Modified: Mon, 08 Dec 2014 19:23:51 GMT
ETag: &amp;quot;adg2jl4kfmm5&amp;quot;
Cache-Control: max-age=533280
Expires: Sun, 03 May 2024 11:02:37 GMT
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We don&amp;#x27;t need to dive deeper into what each header means, since the caching policy will do all the work for us. So depending on the values the server sends, the caching policy will basically perform the following behaviour:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;In case no cached response is available, the system fetches the data from the URL.&lt;/li&gt;&lt;li&gt;In case a cached response is available, not expired and doesn&amp;#x27;t always require revalidation, the system returns the cached response.&lt;/li&gt;&lt;li&gt;In case a cached response is available, but expired or requires revalidation, a HEAD request is send to the server to check if the resource changed. If it&amp;#x27;s changed, the system fetches the data from the URL. Otherwise, it returns the cached response.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;.reloadIgnoringLocalCacheData&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.reloadIgnoringLocalCacheData&lt;/code&gt; policy is self-explanatory, it simply fully ignores local cache data always sending the request to the server.&lt;/p&gt;&lt;h3&gt;.reloadIgnoringLocalAndRemoteCacheData&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.reloadIgnoringLocalAndRemoteCacheData&lt;/code&gt; also fully ignores local cache data. Additionally, it instructs proxies and other intermediates to ignore their caches so far as the protocol allows.&lt;/p&gt;&lt;h3&gt;.returnCacheDataElseLoad&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.returnCacheDataElseLoad&lt;/code&gt; policy always uses cached data if available ignoring its age or expiration date. It sends the request to the server only if there is no cached data.&lt;/p&gt;&lt;h3&gt;.returnCacheDataDontLoad&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.returnCacheDataDontLoad&lt;/code&gt; policy always uses cached data. If no cached data is available, it returns an error. So a request is never send to the server.&lt;/p&gt;&lt;h3&gt;.reloadRevalidatingCacheData&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;.reloadRevalidatingCacheData&lt;/code&gt; policy uses cached data if it can be validated by the server. Otherwise, the request is send to the server.&lt;/p&gt;</content:encoded><category>networking</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to create NSManagedObject subsclasses for Core Data entities in Xcode]]></title><description><![CDATA[Learn about Xcode's code generation options when creating Core Data models in Swift.]]></description><link>https://tanaschita.com/20221024-how-to-create-a-managed-object-subclass-xcode-core-data</link><guid isPermaLink="false">https://tanaschita.com/20221024-how-to-create-a-managed-object-subclass-xcode-core-data</guid><pubDate>Sun, 23 Oct 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As described in this &lt;a href=&quot;/20220919-understanding-the-main-aspects-of-core-data/&quot;&gt;developer guide on the main aspects of Core Data&lt;/a&gt;, each entity we setup in the &lt;code&gt;.xcdatamodeld&lt;/code&gt; file describes an object and has a corresponding class that inherits from &lt;code&gt;NSManagedObject&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Xcode provides three different code generation options to control how those subclasses are created.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/7018812bedfcba0eb16171afe5f6a380/c07fd/20221031-xcodes-entity-codegen-options.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:24.8%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAABLUlEQVQY0zWPzU7DMBCE8w5InJDaxI6d2NnETkraJKWljSooP6rEgTNCHBDiBI/Ae++gWHD4tCOtdmcm0kXNtXfsveeSiMvScb1o2fmGMyKufBN0wNVsm4EtOTamAFHFeW45VRlkqiFThagc7lBcXuNCEGbKYy40rCV03Rp5XiBOJLLcYh6LoOM4QSJSmKLktlvj+HDCZn9A065QdztE1eGV5+MXny3f+bz74Fm+YJkoPl4986ofWGeGV93AMtWsdT6lYZFq9q7GUBFvyPBgNXqjUBUWUao0iEpYW0BIGdyVMmjdBsYShFQoK/9XSUNMM5bYE2F8fMP9yw/60zdo/MR294aISoeJRKhwHB44D980kFIFg6nq/y6RClKkWBqL7XKHm/Ep0C9v0botfgEIl5vZ0x1aWQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s entity codegen options.&quot; title=&quot;Xcode&amp;#x27;s entity codegen options.&quot; src=&quot;/static/7018812bedfcba0eb16171afe5f6a380/2e9ed/20221031-xcodes-entity-codegen-options.png&quot; srcSet=&quot;/static/7018812bedfcba0eb16171afe5f6a380/12d18/20221031-xcodes-entity-codegen-options.png 375w,/static/7018812bedfcba0eb16171afe5f6a380/44507/20221031-xcodes-entity-codegen-options.png 750w,/static/7018812bedfcba0eb16171afe5f6a380/2e9ed/20221031-xcodes-entity-codegen-options.png 1500w,/static/7018812bedfcba0eb16171afe5f6a380/c07fd/20221031-xcodes-entity-codegen-options.png 1820w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s entity codegen options.&lt;/div&gt;&lt;p&gt;As we can see above, the options are &lt;code&gt;Manual/None&lt;/code&gt;, &lt;code&gt;Class Definition&lt;/code&gt; and &lt;code&gt;Category/Extension&lt;/code&gt;. We can choose which option to use for a specific entity in the Data Model inspector by selecting the entity in the &lt;code&gt;.xcdatamodeld&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s take a closer look at what each of that options means.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Codegen option &lt;code&gt;Class Definition&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;This is the default codegen option when we add a new entity. With the &lt;code&gt;Class Definition&lt;/code&gt; option selected, Core Data generates an &lt;code&gt;NSManagedObject&lt;/code&gt; subclass with all entity properties and relationships automatically.&lt;/p&gt;&lt;p&gt;For example, a generated subclass for a &lt;code&gt;User&lt;/code&gt; entity looks as follows:&lt;/p&gt;&lt;p&gt;&lt;em&gt;User+CoreDataClass.swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@objc(User)
public class User: NSManagedObject {
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;User+CoreDataProperties.swift&lt;/em&gt;&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension User {
    @nonobjc public class func fetchRequest() -&amp;gt; NSFetchRequest&amp;lt;User&amp;gt; {
        return NSFetchRequest&amp;lt;User&amp;gt;(entityName: &amp;quot;User&amp;quot;)
    }

    @NSManaged public var username: String?
    @NSManaged public var lastLogin: Date?
    @NSManaged public var verified: Bool
    @NSManaged public var posts: NSSet?
}

extension User {
    @objc(addPostsObject:)
    @NSManaged public func addToPosts(_ value: Post)

    @objc(removePostsObject:)
    @NSManaged public func removeFromPosts(_ value: Post)

    @objc(addPosts:)
    @NSManaged public func addToPosts(_ values: NSSet)

    @objc(removePosts:)
    @NSManaged public func removeFromPosts(_ values: NSSet)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see above, Core Data creates a class file and an extension file. These files don&amp;#x27;t appear in our source code. Xcode produces them as part of the build process and places them in the projects build directory. After building the project, we are able to reference the &lt;code&gt;User&lt;/code&gt; entity in our source code. The files are regenerated every time we change the corresponding entity.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; If Xcode throws a compiler error saying &lt;code&gt;Cannot find type &amp;#x27;User&amp;#x27; in scope&lt;/code&gt;, try to clean build the project.&lt;/p&gt;&lt;h3&gt;Codegen option &lt;code&gt;Category/Extension&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;With the &lt;code&gt;Category/Extension&lt;/code&gt; option selected, Xcode only generates the properties file &lt;em&gt;User+CoreDataProperties.swift&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;We are responsible to generate the class file ourselves which gives us more control for example to add additional methods or business logic inside the managed object subclass.&lt;/p&gt;&lt;p&gt;To generate the class file initially, we can use Xcode&amp;#x27;s help by selecting &lt;code&gt;Editor &amp;gt; Create NSManagedObject Subclass&lt;/code&gt; in the menu.&lt;/p&gt;&lt;h3&gt;Codegen option &lt;code&gt;Manual/None&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;When using the &lt;code&gt;Manual/None&lt;/code&gt; option, Core Data doesn&amp;#x27;t generate any files. We create and maintain the subclass and all its properties and relationsships ourselves.&lt;/p&gt;&lt;p&gt;Core Data locates these files using the values we supply in the class name and module fields in Xcode&amp;#x27;s Data Model inspector when selecting an entity in the &lt;code&gt;.xcdatamodeld&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;To generate a class and properties file initially, we can use Xcode&amp;#x27;s help by selecting &lt;code&gt;Editor &amp;gt; Create NSManagedObject Subclass&lt;/code&gt; in the menu.&lt;/p&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The decision whether to let Xcode automatically generate entity subclasses and their properties for us or to create and manage them manually depends on individual project needs and developer preferences.&lt;/p&gt;&lt;p&gt;The automatic generation option has the advantage that we don&amp;#x27;t have to worry about keeping the entity classes up-to-date, Xcode will do that for us.&lt;/p&gt;&lt;p&gt;The manual option on the other hand provides us with more control and flexibility, for example to improve the handling of non-optional properties. If you are interested to learn more, checkoout this article on &lt;a href=&quot;/20221114-how-to-handle-non-optional-core-data-values-in-swift/&quot;&gt;how to handle non-optional Core Data properties in Swift&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>xcode</category><category>ios</category></item><item><title><![CDATA[iOS developer guide on the main aspects of Core Data]]></title><description><![CDATA[Understand the main concepts of the Core Data framework.]]></description><link>https://tanaschita.com/20220919-understanding-the-main-aspects-of-core-data</link><guid isPermaLink="false">https://tanaschita.com/20220919-understanding-the-main-aspects-of-core-data</guid><pubDate>Sun, 18 Sep 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;To persist complex data structures in an iOS application, Apple provides us with the &lt;em&gt;Core Data&lt;/em&gt; framework. Core Data allows us to store permanent data for offline use, to cache data, to add undo functionality and more.&lt;/p&gt;&lt;p&gt;In this developer guide, we&amp;#x27;ll look at the main concepts of Core Data.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Core Data model&lt;/h3&gt;&lt;p&gt;In Core Data, we define object structures by using entities that can have attributes as properties and relationships between each other.&lt;/p&gt;&lt;p&gt;A Core Data model is represented by a &lt;code&gt;.xcdatamodeld&lt;/code&gt; file. Within the file, we can use Xcode&amp;#x27;s built-in editor to add entities, their attributes, define relationships and more.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/721214f515f1ab3dd2a99717939ac391/84681/20220919-xcdatamodeld-xcode-editor.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:34.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABa0lEQVQoz2XR627aQBCGYV9Io2Lvwbte22t7fQIbAk0IFW1VlFRCidT7v4y3gtKT+uPRzL+Z+Sbqxom6aWlCTzvMPByfefxy5nB6Zd7uWa3W9MOSYVwxr7ds7j8wrWZK31DVgdLXpMaRSENoe6KX529M8z3G5uRVjw8jrmyowoTLK3TqENoitUWoG6GJE00sNFJZxuVMP64Zpx3R29t3HvcfCe1A4QN57okTgc9aKt9ijUGKBCnEP9SvXmp2D3uOx89sDyei8/mVr6cXun6JTi1CpiSJou4minpA+B1J+MSiOnBXPHFX/vGueELahia0VHVL149Em83umk3bDShtSYQmEYo085i8IbGBJBuIbc/CdMS2+13fmxZtPWVZY7OCJnREeVFhXXGlLznJSy6GvPB4XyOlwlzOlvI/SimUSpEqZRHL64MiV3hcXpK5ElfUuLzAZg6pNFqnaG1u0r8YlE7JXP5zcBVuG/b8AKSn2Na+eLu+AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Xcode&amp;#x27;s .xcdatamodeld file editor&quot; title=&quot;Xcode&amp;#x27;s .xcdatamodeld file editor&quot; src=&quot;/static/721214f515f1ab3dd2a99717939ac391/2e9ed/20220919-xcdatamodeld-xcode-editor.png&quot; srcSet=&quot;/static/721214f515f1ab3dd2a99717939ac391/12d18/20220919-xcdatamodeld-xcode-editor.png 375w,/static/721214f515f1ab3dd2a99717939ac391/44507/20220919-xcdatamodeld-xcode-editor.png 750w,/static/721214f515f1ab3dd2a99717939ac391/2e9ed/20220919-xcdatamodeld-xcode-editor.png 1500w,/static/721214f515f1ab3dd2a99717939ac391/e90d0/20220919-xcdatamodeld-xcode-editor.png 2250w,/static/721214f515f1ab3dd2a99717939ac391/9d489/20220919-xcdatamodeld-xcode-editor.png 3000w,/static/721214f515f1ab3dd2a99717939ac391/84681/20220919-xcdatamodeld-xcode-editor.png 3450w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Xcode&amp;#x27;s .xcdatamodeld file editor&lt;/div&gt;&lt;p&gt;Internally, the &lt;code&gt;.xcdatamodeld&lt;/code&gt; file is an XML file. We can open it in any text editor to inspect it.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/7ef578b988e5b0bc8ea665e546f050a5/cfc2e/20220919-xcdatamodeld-xml-content.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:34.400000000000006%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABqUlEQVQoz32PXWvTYABG+3cUcV0n9iPN0jfJm+Zt0iZN2gwZ/hovFBwU3Uft1rkVBC8c4gcO/4IXQxFBZYLoGHbtpjdOvTvSdO7Si3P3cDhP5sP+Rz59/sLXg0Ne7b3h1tJdbtzs0On2Wb//gNXNbTqrPTrLPW4vd1m6c4+V3hYr61usbQzoTugP6G4OWOsPyLx7v8/e67ccHA7ZfrjD5dw8pl5HVwGGFyH9BFlLEM0WZdXEkm0MGaHJAGHHCBmjiYCC6ZPVHDJH4xO+HY05/fWHR09ecKlo4VdjQpUgvBaOfw1PLSKCBMONqVRbCNXCdhMqboyhYjQ7TIVXKorM+OQHE+nP09/sPN3lwtUKwvSJrADpL2BEMTVvEbORYHsL+N51RLNNpdbCdtqpuKRC5gxFTq+SGR1/Zzg6PhdezAuUHdCwIwy3ybwbYVXbaCpMS4p2A80OUvK2T1HUKYk6edNnznCnhcOzwsfPXzJTktRMDzkZOQ00cyooWg3KVpAKS06ALkOKTkBWd8hqktmykzIVjs4uP9tlpmBRMBSzunM++h+5cjW9+o+/IHY8qCS/kQwAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;.xcdatamodeld file&amp;#x27;s XML content.&quot; title=&quot;.xcdatamodeld file&amp;#x27;s XML content.&quot; src=&quot;/static/7ef578b988e5b0bc8ea665e546f050a5/2e9ed/20220919-xcdatamodeld-xml-content.png&quot; srcSet=&quot;/static/7ef578b988e5b0bc8ea665e546f050a5/12d18/20220919-xcdatamodeld-xml-content.png 375w,/static/7ef578b988e5b0bc8ea665e546f050a5/44507/20220919-xcdatamodeld-xml-content.png 750w,/static/7ef578b988e5b0bc8ea665e546f050a5/2e9ed/20220919-xcdatamodeld-xml-content.png 1500w,/static/7ef578b988e5b0bc8ea665e546f050a5/e90d0/20220919-xcdatamodeld-xml-content.png 2250w,/static/7ef578b988e5b0bc8ea665e546f050a5/9d489/20220919-xcdatamodeld-xml-content.png 3000w,/static/7ef578b988e5b0bc8ea665e546f050a5/cfc2e/20220919-xcdatamodeld-xml-content.png 3200w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;.xcdatamodeld file&amp;#x27;s XML content&lt;/div&gt;&lt;h3&gt;Managed objects&lt;/h3&gt;&lt;p&gt;Each entity we setup in the &lt;code&gt;.xcdatamodeld&lt;/code&gt; file describes an object and has a corresponding class that inherits from &lt;code&gt;NSManagedObject&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;NSManagedObject&lt;/code&gt; subclass and properties extension for the &lt;code&gt;User&lt;/code&gt; entity might look as follows:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@objc(User)
public class User: NSManagedObject {
}

extension User {
    @NSManaged public var username: String?
    @NSManaged public var lastLogin: Date?
    @NSManaged public var verified: Bool
    @NSManaged public var posts: NSSet?
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each attribute has an &lt;code&gt;@NSManaged&lt;/code&gt; annotation which indicates that Core Data dynamically provides its implementation at runtime, based on the associated entity description.&lt;/p&gt;&lt;p&gt;The &lt;code&gt;NSManagedObject&lt;/code&gt; subclasses can be generated automatically or manually. Checkout this article on &lt;a href=&quot;/20221024-how-to-create-a-managed-object-subclass-xcode-core-data/&quot;&gt;how to create an NSManagedObject subsclasses for Core Data entities in Xcode&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;Managed object context&lt;/h3&gt;&lt;p&gt;Represented by the &lt;code&gt;NSManagedObjectContext&lt;/code&gt; type, a context has a central role in the life cycle of managed objects.&lt;/p&gt;&lt;p&gt;It allows us to retrieve objects from a persistent store, create new objects, delete and edit objects. These changes remain in memory until we either discard the changes or save them back to the persistent store.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let newUser = User(context: managedObjectContext)
newUser.username = ...
// setting more attributes

do {
    try managedObjectContext.save()
} catch {
    // handle error
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we create a new user and then use the &lt;code&gt;save()&lt;/code&gt; method on the context to save the user.&lt;/p&gt;&lt;h3&gt;Fetch requests&lt;/h3&gt;&lt;p&gt;Represented by &lt;code&gt;NSFetchRequest&lt;/code&gt;, we can use a fetch request to define which managed objects we want to request from the managed context.&lt;/p&gt;&lt;p&gt;An example to fetch all users might look like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;let usersFetchRequest = NSFetchRequest&amp;lt;User&amp;gt;(entityName: &amp;quot;User&amp;quot;)
usersFetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \User.username, ascending: true)]

do {
    let users = try viewContext.execute(usersFetchRequest)
} catch {
    // handle error
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As we can see in the example above, a fetch request specifies which entity to search. Optionally, we can add a predicate to filter the results, a sort descriptor, a fetch limit and more.&lt;/p&gt;&lt;p&gt;When working with SwiftUI, we can use the &lt;code&gt;@FetchRequest&lt;/code&gt; property wrapper to retrieve data from Core Data.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;@FetchRequest(
  sortDescriptors: [NSSortDescriptor(keyPath: \User.username, ascending: true)])
private var users: FetchedResults&amp;lt;User&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checkout this article on &lt;a href=&quot;/swiftui-fetchrequest-property-wrapper/&quot;&gt;@FetchRequest property wrapper in SwiftUI&lt;/a&gt; to learn more.&lt;/p&gt;&lt;h3&gt;NSPersistentContainer&lt;/h3&gt;&lt;p&gt;The &lt;code&gt;NSPersistentContainer&lt;/code&gt; class simplifies creating and managing the Core Data stack.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;container = NSPersistentContainer(name: &amp;quot;CoreDataExampleApp&amp;quot;)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    // Handle error if needed.
})

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the example above, we create an instance of &lt;code&gt;NSPersistentContainer&lt;/code&gt; and call &lt;code&gt;loadPersistentStores()&lt;/code&gt; on it. When successfully setup, it provides the &lt;code&gt;viewContext&lt;/code&gt; property that gives us access to a managed object context to start working with Core Data.&lt;/p&gt;</content:encoded><category>core data</category><category>persistence</category><category>xcode</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to manage view lifecycle events in SwiftUI]]></title><description><![CDATA[Discover SwiftUI's view lifecycle events onAppear, onDisappear, task, and task(id:).]]></description><link>https://tanaschita.com/swiftui-view-lifecycle-events</link><guid isPermaLink="false">https://tanaschita.com/swiftui-view-lifecycle-events</guid><pubDate>Wed, 14 Sep 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with SwiftUI views, we often need to trigger side effects when a view appears or disappears. We can do that by using declarative modifiers that attach to views.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;The onAppear(perform:) method&lt;/h3&gt;&lt;p&gt;&lt;code&gt;onAppear&lt;/code&gt; runs right before a view appears. It is useful for lightweight setup or triggering updates.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeView: View {

    var body: some View {
        VStack {
            ...
        }
        .onAppear {
            viewModel.update()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The onDisappear(perform:) method&lt;/h3&gt;&lt;p&gt;&lt;code&gt;onDisappear&lt;/code&gt; runs right after the view disappears. It&amp;#x27;s the place to clean up or cancel ongoing work.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeView: View {

    var body: some View {
        VStack {
            ...
        }
        .onDisappear {
            viewModel.cancelLoading()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The task(priority:_:) method&lt;/h3&gt;&lt;p&gt;The task modifier allows us to start asynchronous work when a view appears. SwiftUI automatically cancels the task when the view disappears, so it integrates neatly with the view lifecycle.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct SomeView: View {

    var body: some View {
        VStack {
            ...
        }
        .task {
            await viewModel.load()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The task(id:) variant&lt;/h3&gt;&lt;p&gt;&lt;code&gt;task(id:)&lt;/code&gt; is a more advanced form of task. It still behaves like the regular task in that it runs once when the view appears and is canceled when the view disappears. The difference is that it also observes a value we provide as an identifier. Whenever that value changes, SwiftUI cancels the old task and starts a new one automatically.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.task(id: viewModel.query) {
    await viewModel.search(query: viewModel.query)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the search runs once when the view appears, and then again every time the query changes. This makes &lt;code&gt;task(id:)&lt;/code&gt; especially useful for scenarios like search bars or dynamic filters, where you want to restart asynchronous work when a parameter updates, without having to manage cancellation manually.&lt;/p&gt;&lt;p&gt;With these tools, SwiftUI provides a clear way to run both synchronous and asynchronous work in response to view lifecycle changes.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Building configurable widgets with WidgetKit and SwiftUI]]></title><description><![CDATA[Learn how to let users customize a WidgetKit widget with App Intents.]]></description><link>https://tanaschita.com/widgetkit-configurable-widgets-swiftui</link><guid isPermaLink="false">https://tanaschita.com/widgetkit-configurable-widgets-swiftui</guid><pubDate>Sun, 04 Sep 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the previous article on &lt;a href=&quot;/widgetkit-building-widgets-with-swiftui&quot;&gt;building widgets with WidgetKit and SwiftUI&lt;/a&gt;, we looked at the basic structure of a WidgetKit widget.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at configurable widgets. A configurable widget lets users choose values that affect what the widget displays. For example, a finance app could let users choose an account, a task app could let users choose a project, and a quote app could let users choose a category.&lt;/p&gt;&lt;p&gt;We&amp;#x27;ll use a simple quote widget as an example. The user will be able to choose a quote category when adding or editing the widget, and the widget will show a quote for that selected category.&lt;/p&gt;&lt;p&gt;The flow is: the user chooses a value, WidgetKit stores it in the intent, the provider receives the intent, and the widget renders an entry built from that value.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Defining the configurable value&lt;/h3&gt;&lt;p&gt;The first step is to define the value the user can choose. In our example, this value is the quote category.&lt;/p&gt;&lt;p&gt;For a fixed list of options, we can use an enum that conforms to &lt;code&gt;AppEnum&lt;/code&gt;. Dynamic options are also possible when the available values come from app data, and we&amp;#x27;ll look at that later in the article.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import AppIntents

enum QuoteCategory: String, AppEnum {
    case motivation
    case focus
    case mindfulness

    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: &amp;quot;Category&amp;quot;)

    static var caseDisplayRepresentations: [QuoteCategory: DisplayRepresentation] = [
        .motivation: &amp;quot;Motivation&amp;quot;,
        .focus: &amp;quot;Focus&amp;quot;,
        .mindfulness: &amp;quot;Mindfulness&amp;quot;
    ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The display representations define how the enum cases appear in the widget configuration UI.&lt;/p&gt;&lt;h3&gt;Exposing the value to WidgetKit&lt;/h3&gt;&lt;p&gt;Next, we define a type that conforms to &lt;code&gt;WidgetConfigurationIntent&lt;/code&gt;. The intent describes the values WidgetKit should show when the user configures the widget.&lt;/p&gt;&lt;p&gt;We can use the &lt;code&gt;QuoteCategory&lt;/code&gt; enum as a parameter:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteWidgetIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = &amp;quot;Quote Widget&amp;quot;
    static var description = IntentDescription(&amp;quot;Shows a quote from the selected category.&amp;quot;)

    @Parameter(title: &amp;quot;Category&amp;quot;)
    var category: QuoteCategory = .motivation
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;@Parameter&lt;/code&gt; property is the value users can change. Here, the user can choose a quote category, and the widget can later use that selected category to decide which quote to display.&lt;/p&gt;&lt;h3&gt;Connecting the intent to the widget&lt;/h3&gt;&lt;p&gt;For a configurable widget, we use &lt;code&gt;AppIntentConfiguration&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import AppIntents
import SwiftUI
import WidgetKit

struct QuoteWidget: Widget {
    var body: some WidgetConfiguration {
        AppIntentConfiguration(
            kind: &amp;quot;com.example.QuoteWidget&amp;quot;,
            intent: QuoteWidgetIntent.self,
            provider: QuoteTimelineProvider()
        ) { entry in
            QuoteWidgetView(entry: entry)
        }
        .configurationDisplayName(&amp;quot;Quote&amp;quot;)
        .description(&amp;quot;Shows a quote for the selected category.&amp;quot;)
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;WidgetKit uses the intent type to show the widget configuration UI and to pass the selected values to the timeline provider.&lt;/p&gt;&lt;h3&gt;Using the selected value&lt;/h3&gt;&lt;p&gt;A timeline entry contains the data that the widget view needs to render a specific state:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteEntry: TimelineEntry {
    let date: Date
    let category: QuoteCategory
    let quote: String
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Because the widget is configurable, the provider conforms to &lt;code&gt;AppIntentTimelineProvider&lt;/code&gt;. Its &lt;code&gt;snapshot&lt;/code&gt; and &lt;code&gt;timeline&lt;/code&gt; methods receive the selected configuration, which is where the user choice becomes useful:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteTimelineProvider: AppIntentTimelineProvider {
    func placeholder(in context: Context) -&amp;gt; QuoteEntry {
        QuoteEntry(
            date: Date(),
            category: .motivation,
            quote: &amp;quot;Small steps count.&amp;quot;
        )
    }

    func snapshot(for configuration: QuoteWidgetIntent, in context: Context) async -&amp;gt; QuoteEntry {
        entry(for: configuration.category)
    }

    func timeline(for configuration: QuoteWidgetIntent, in context: Context) async -&amp;gt; Timeline&amp;lt;QuoteEntry&amp;gt; {
        let entry = entry(for: configuration.category)
        let nextUpdate = Date().addingTimeInterval(60 * 60)

        return Timeline(entries: [entry], policy: .after(nextUpdate))
    }

    private func entry(for category: QuoteCategory) -&amp;gt; QuoteEntry {
        QuoteEntry(
            date: Date(),
            category: category,
            quote: quote(for: category)
        )
    }

    private func quote(for category: QuoteCategory) -&amp;gt; String {
        switch category {
        case .motivation:
            return &amp;quot;Small steps count.&amp;quot;
        case .focus:
            return &amp;quot;One thing at a time.&amp;quot;
        case .mindfulness:
            return &amp;quot;Take a breath before moving on.&amp;quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The important part is &lt;code&gt;configuration.category&lt;/code&gt;. It contains the category selected by the user, so the provider can create a timeline entry for that category.&lt;/p&gt;&lt;h3&gt;Building the widget view&lt;/h3&gt;&lt;p&gt;The widget view receives the timeline entry and renders the selected content:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct QuoteWidgetView: View {
    let entry: QuoteEntry

    var body: some View {
        Text(entry.quote)
            .font(.headline)
            .containerBackground(.background, for: .widget)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In a real app, the provider could load the quote from shared app data, a local database, or another persistence layer that the app and widget extension can both access.&lt;/p&gt;&lt;h3&gt;When options come from app data&lt;/h3&gt;&lt;p&gt;An enum works well when the available choices are known ahead of time. If the options come from user data, such as projects, accounts, or lists, the configuration can use App Intents entities and queries instead.&lt;/p&gt;&lt;p&gt;For example, a task app might let users choose one of their own projects. In that case, the widget configuration needs to load the available projects dynamically instead of defining them as enum cases.&lt;/p&gt;&lt;h3&gt;Summary&lt;/h3&gt;&lt;p&gt;To build a configurable widget, we define a &lt;code&gt;WidgetConfigurationIntent&lt;/code&gt;, use it with &lt;code&gt;AppIntentConfiguration&lt;/code&gt;, and read the selected values in an &lt;code&gt;AppIntentTimelineProvider&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;For simple fixed choices, an &lt;code&gt;AppEnum&lt;/code&gt; is often enough. For user-generated choices, App Intents entities are a better fit because the available options can be loaded dynamically.&lt;/p&gt;</content:encoded><category>widgetkit</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to bridge async/await functions to Combine's Future type in Swift]]></title><description><![CDATA[Learn how to call async/await code within Combine based APIs.]]></description><link>https://tanaschita.com/20220822-bridge-async-await-to-combine-future</link><guid isPermaLink="false">https://tanaschita.com/20220822-bridge-async-await-to-combine-future</guid><pubDate>Sun, 21 Aug 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with asynchronious code in Swift, we might have to find ways to mix and connect different asynchronious patterns like using the Combine framework together with Swift&amp;#x27;s async/await API.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to call &lt;code&gt;async&lt;/code&gt;-marked functions within Combine based APIs.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Let&amp;#x27;s look at the following &lt;code&gt;async&lt;/code&gt; function which loads a user from server with the &lt;code&gt;async/await&lt;/code&gt; pattern.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func loadUser() async throws -&amp;gt; User {
    // Load user from server
    return user // or throw error
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To implement the same behaviour with Combine, its &lt;code&gt;Future&lt;/code&gt; publisher comes in handy. A future is initialized with a closure that takes a &lt;code&gt;Future.Promise&lt;/code&gt;. After doing some asynchronous work, we call that closure with a &lt;code&gt;Result&lt;/code&gt; that is either a success or a failure. Combine then automatically maps the result into proper publisher events.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s look at how the async function above could be implemented with a &lt;code&gt;Future&lt;/code&gt; publisher:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func loadUser() -&amp;gt; Future&amp;lt;User, Error&amp;gt; {
    return Future() { promise in
        // Load user from server.
        promise(.success(user))  // or promise(.failure(error))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But since we already have an implementation that loads a user, we&amp;#x27;d like to avoid duplication writing the same logic twice.&lt;/p&gt;&lt;p&gt;So let&amp;#x27;s try to find a more generic solution, that allows us to convert an async func to a &lt;code&gt;Future&lt;/code&gt; publisher.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension Future where Failure == Error {
    convenience init(asyncFunc: @escaping () async throws -&amp;gt; Output) {
        self.init { promise in
            Task {
                do {
                    let result = try await asyncFunc()
                    promise(.success(result))
                } catch {
                    promise(.failure(error))
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the code above, we extend the &lt;code&gt;Future&lt;/code&gt; type with a convenience initializer that allows us to initialize an instance with an &lt;code&gt;async&lt;/code&gt; closure. Applied to our example:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;func loadUser() -&amp;gt; Future&amp;lt;Int, Error&amp;gt; {
    return Future(asyncFunc: {
        try await loadUser()
    })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For non-throwing async functions, we could add another extension:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;extension Future where Failure == Never {

    convenience init(asyncFunc: @escaping () async -&amp;gt; Output) {
        self.init { promise in
            Task {
                let result = await asyncFunc()
                promise(.success(result))
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With a generic solution like this, we now have the possibility to use it on any &lt;code&gt;async&lt;/code&gt; function to bridge it to Combine&amp;#x27;s &lt;code&gt;Future&lt;/code&gt; type.&lt;/p&gt;</content:encoded><category>combine</category><category>async/await</category><category>concurrency</category><category>reactive programming</category><category>swift</category></item><item><title><![CDATA[Implementing Password AutoFill for an iOS application]]></title><description><![CDATA[Learn how to activate Apple's Password AutoFill for SwiftUI and UIKit.]]></description><link>https://tanaschita.com/20220815-supporting-password-autofill-for-ios-appilcations</link><guid isPermaLink="false">https://tanaschita.com/20220815-supporting-password-autofill-for-ios-appilcations</guid><pubDate>Sun, 14 Aug 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apple&amp;#x27;s Password AutoFill feature allows users to login into the same account on different devices without having to remember their credentials.&lt;/p&gt;&lt;p&gt;For example, when signing up in a web app, users are able to generate a password and to save credentials in their iCloud keychain. When they run a native app to access the same account at some point, the app suggests the credentials stored for the website in the password QuickType bar. After the user authenticates, let&amp;#x27;s say by using Face ID, the system prefills the credentials - so there is no need to reenter them.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;To activate Password Autofill in an iOS application, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Setting up the app&amp;#x27;s associated domains.&lt;/li&gt;&lt;li&gt;Setting the correct &lt;code&gt;AutoFill&lt;/code&gt; type on relevant text fields.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Let&amp;#x27;s look at those steps in more detail.&lt;/p&gt;&lt;h3&gt;Setting up the app&amp;#x27;s associated domains for shared web credentials&lt;/h3&gt;&lt;p&gt;Associated domains establish a secure association between a website and an iOS application. To support shared web credentials, the following steps are required:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Creating and configuring an associated domain file for shared web credentials.&lt;/li&gt;&lt;li&gt;Adding the associated domain file to the related website.&lt;/li&gt;&lt;li&gt;Adding an associated domain entitlement to the iOS app.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Each of those steps is described in more detail in this &lt;a target=&quot;_blank&quot; href=&quot;/20220725-quick-guide-on-associated-domains-in-ios/&quot;&gt;quide on associated domain files&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Enable Password Autofill with &lt;code&gt;textContentType&lt;/code&gt;&lt;/h3&gt;&lt;p&gt;After setting up shared web credentials, all we need to do is to set the &lt;code&gt;textContentType&lt;/code&gt; property on any relevant input views to activate the correct AutoFill suggestions.&lt;/p&gt;&lt;p&gt;The following &lt;code&gt;UITextContentType&lt;/code&gt; values are available:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.username&lt;/code&gt; for account names&lt;/li&gt;&lt;li&gt;&lt;code&gt;.password&lt;/code&gt; for passwords&lt;/li&gt;&lt;li&gt;&lt;code&gt;.newPassword&lt;/code&gt; for new passwords&lt;/li&gt;&lt;li&gt;&lt;code&gt;.oneTimeCode&lt;/code&gt; for one-time passwords, for example to autocomplete security codes from single-factor sms login flows&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s see how to do that for SwiftUI and UIKit applications.&lt;/p&gt;&lt;h4&gt;Enable Password Autofill for SwiftUI&lt;/h4&gt;&lt;p&gt;In SwiftUI, we can set a &lt;code&gt;UITextContentType&lt;/code&gt; value by using the &lt;code&gt;textContentType(_:)&lt;/code&gt; method:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;TextField(&amp;quot;Enter your email&amp;quot;, text: $emailAddress)
    .textContentType(.username)
    .keyboardType(.emailAddress)

TextField(&amp;quot;Enter your password&amp;quot;, text: $password)
    .textContentType(.password)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Based on &lt;code&gt;textContentType&lt;/code&gt; we provided, iOS automatically chooses a matching keyboard to show to the user.&lt;/p&gt;&lt;p&gt;To present a different keyboard, for example if the app uses emails as user names, we can additionally set the &lt;code&gt;.keyboardType&lt;/code&gt; to a desired value.&lt;/p&gt;&lt;h4&gt;Enable Password Autofill for UIKit&lt;/h4&gt;&lt;p&gt;UIKit supports Password AutoFill on &lt;code&gt;UITextField&lt;/code&gt;, &lt;code&gt;UITextView&lt;/code&gt; and any custom view that adopts the &lt;code&gt;UITextInput&lt;/code&gt; protocol. We can set a &lt;code&gt;UITextContentType&lt;/code&gt; value by using the &lt;code&gt;textContentType&lt;/code&gt; instance property:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;userTextField.textContentType = .username
userTextField.keyboardType = .emailAddress
passwordTextField.textContentType = .password
&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>associated domains</category><category>security</category><category>swiftui</category><category>ios</category></item><item><title><![CDATA[Quick guide on Proxyman for iOS development]]></title><description><![CDATA[Learn how to use Proxyman to inspect network traffic for your iOS application.]]></description><link>https://tanaschita.com/20220704-quick-guide-on-proxyman</link><guid isPermaLink="false">https://tanaschita.com/20220704-quick-guide-on-proxyman</guid><pubDate>Sun, 03 Jul 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Proxyman is a proxy server app available for macOS that enables us to view networking traffic, including traffic from iOS applications. It provides the possibility to configure the iOS simulator or device to pass its HTTP and HTTPS requests and responses through Proxyman, so we are able to inspect and even change the data in our testing and debugging process.&lt;/p&gt;&lt;p&gt;This guide provides an overview on Proxyman and its features with a focus on iOS development.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Recap on proxies&lt;/h3&gt;&lt;p&gt;A proxy is an application that acts as an intermediary between a client requesting a resource and the server providing that resource. So instead of connecting directly to a server, the client directs the request to the proxy, which performs the request, receives the response and then directs the response to the client.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1324px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:15.466666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAgUlEQVQI142OSwqDMABEvf/ZlAbjASxBFNOkRhOM31cIbSmli77NLIZ5TMaT8zxTHttGMJZgLfuyEJ3DG0OcJvYYCca8u8/di+xbGIcBJSXXsqTPc1ohqKWkuQhuRYGqKmoh8L3+T3isK2Pb4pqG1fv08K4UQev0cOy61G3z/FP4ANum5zHXertVAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Proxy as interactor between client and server.&quot; title=&quot;Proxy as interactor between client and server.&quot; src=&quot;/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png&quot; srcSet=&quot;/static/70e51500a59e00281577c96a511566e3/12d18/20220228_proxy.png 375w,/static/70e51500a59e00281577c96a511566e3/44507/20220228_proxy.png 750w,/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png 1324w&quot; sizes=&quot;(max-width: 1324px) 100vw, 1324px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Proxy as interactor between client and server.&lt;/div&gt;&lt;h3&gt;Getting started with Proxyman&lt;/h3&gt;&lt;p&gt;Proxyman provides a free version with all essential features that can be downloaded on the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://docs.proxyman.io/#download&quot;&gt;official download page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After installing and launching the app, it asks for permission to install a proxy helper tool to configure system proxy settings. When finished, Proxyman directly starts recording network traffic.&lt;/p&gt;&lt;p&gt;When calling a URL, for example in a browser, the requests pop up in the flow list
panel.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/32b6559ea50aadfb4b47b07a3351965b/8c3ac/20220704_proxyman_startscreen_panels.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:58.666666666666664%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAABYlAAAWJQFJUiTwAAACAUlEQVQozy3Ru27bQBAFUH5FEIm781rui2+KpCSTlGwnsmOkcZvKMBLEgYv8fxtQDnCKW1zMDDDJtG+balXHvHS2jO48He6n6ctp/nqap3EYu/bYd/M4VMEV1sTM1MGVMa/zmBQ2MoqAboan+vFvdf+bjq/p+KLGl03/Ek6v3eWXmX82D+/h9o8sb83l3Z3fstMq2Y+HdtfHouy7djc/zpfn0J2U20EYUrvz7Xx+eM6Hu+H0VAx3EMb9+butJ4ijdn1SFmXMS5aszti7LI/eWmGCFSrOQhZKZgjeZcKMOnhnmAi1ECSgMdWogAIbFsMs9BGuDLMlzESIhFhYDNL/gohJACW98sQK6bNiIEZiZNHMlqliiIwosmIhMR9YTFIiFKg9qENRX6bDj2+7fduW5a4OsQ15HWLhQulC5fxVyI11yA7ZIydm3UIIOJR1XTXLmBd5EWLFZKz1RjKtADQiENN6vFagUq1SrVOdILJCToHavPR59WmLLhaH4xSKWpNsNaprgYzNXBDrNYlG1siAnNA1KeRorGROI2UuFFUTYy5iAAiRgaTp+uV0e7xZMhcUECAjSQJAKZACMho3qd4q2KRqo9AC5LgKWjkfj9MyLed5OTddr5G313LifbQhsrF9WXe7oen6YTwA8vY6SAOZzCmgTao3W7VJNfH6rbrpbm7mf2JQcaIcuMHTAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Proxyman start screen.&quot; title=&quot;Proxyman start screen.&quot; src=&quot;/static/32b6559ea50aadfb4b47b07a3351965b/2e9ed/20220704_proxyman_startscreen_panels.png&quot; srcSet=&quot;/static/32b6559ea50aadfb4b47b07a3351965b/12d18/20220704_proxyman_startscreen_panels.png 375w,/static/32b6559ea50aadfb4b47b07a3351965b/44507/20220704_proxyman_startscreen_panels.png 750w,/static/32b6559ea50aadfb4b47b07a3351965b/2e9ed/20220704_proxyman_startscreen_panels.png 1500w,/static/32b6559ea50aadfb4b47b07a3351965b/e90d0/20220704_proxyman_startscreen_panels.png 2250w,/static/32b6559ea50aadfb4b47b07a3351965b/8c3ac/20220704_proxyman_startscreen_panels.png 2586w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Proxyman start screen.&lt;/div&gt;&lt;p&gt;Selecting a request in the flow list panel shows request and response details in the flow content panel.&lt;/p&gt;&lt;h3&gt;Enabling SSL Proxying&lt;/h3&gt;&lt;p&gt;As shown above, we cannot inspect the HTTPS response yet because it is encrypted. When communicating over HTTPS, encryption prevents proxy servers and other middleware from eavesdropping on sensitive data. SSL/TLS encrypts the networking traffic using certificates generated by trusted certificate issuers.&lt;/p&gt;&lt;p&gt;To change that we need to install the Proxyman Root Certificate. Proxyman will then act as a man-in-the-middle, which enables us to see the HTTPS traffic in plain text.&lt;/p&gt;&lt;p&gt;To install the Proxyman Root Certificate:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Open &lt;code&gt;Certificate ▸ Install Certificate on this Mac&lt;/code&gt; in Proxyman&amp;#x27;s menu.&lt;/li&gt;&lt;li&gt;Select &lt;code&gt;Install &amp;amp; Trust&lt;/code&gt; in the opened window and enter your password when prompted.&lt;/li&gt;&lt;li&gt;Open &lt;code&gt;Tools ▸ SSL Proxying List&lt;/code&gt; &amp;gt; add a specific domain, a wildcard or and app to enable SSL proxying.&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b2d4248b37f3d755e68214565dc4bf1d/f0b95/20220704_proxyman_ssllist.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:58.13333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABeElEQVQoz42TTU/jMBCG/R9AC03sGdtxPkS/4hBKoSB2L3vhwKFFQmpP/IH9/6d3FSetQnYDHB5N3klm/I5HEeunB9zWG6zqe/hFjbLHYlZhVd9hs35EVdZYTEssZ36cuYe4ua4wnxWYXmXIUxPInA6o+AKTy3MoZrisQKwYkSREagRJEMtf72C/hVpuQeUO5HdtLHdg/xp0kl/Dzz1yd4XU5shs8S9JAWMchH18R3RzwJnf47w69Nh3HBC7EkQMYgvFBjRA9aKIJz/CaPQZzJBkIEmPw7ptqNhCfoHqnDWFjUvSLWySU14dHfbFkOG7RmvrYF2GJM3Dopqm326oWP/3kCPN1of1QoaHj2MNi5rGRz02SfiONASpGDKOIGUEqdTJ2bHYuBxs07DF8Wk0rMtBJoUo7rbgxW9Q+QzOaygiKN26ZWPhVw/IZjVs4iBHrqY5rFptYAsP8fPlD6qnPYr1G9x0g0kUnf6INqoO6nI9ulzDJJZB/wX73VOV1s3FlgAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;SSL Proxying List window&quot; title=&quot;SSL Proxying List window&quot; src=&quot;/static/b2d4248b37f3d755e68214565dc4bf1d/2e9ed/20220704_proxyman_ssllist.png&quot; srcSet=&quot;/static/b2d4248b37f3d755e68214565dc4bf1d/12d18/20220704_proxyman_ssllist.png 375w,/static/b2d4248b37f3d755e68214565dc4bf1d/44507/20220704_proxyman_ssllist.png 750w,/static/b2d4248b37f3d755e68214565dc4bf1d/2e9ed/20220704_proxyman_ssllist.png 1500w,/static/b2d4248b37f3d755e68214565dc4bf1d/f0b95/20220704_proxyman_ssllist.png 2120w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;SSL Proxying List window.&lt;/div&gt;&lt;p&gt;After finishing the steps, we are now able to read encrypted responses from the domains and apps we listed.&lt;/p&gt;&lt;h3&gt;Inspecting network traffic from an iOS simulator or device&lt;/h3&gt;&lt;p&gt;To be able to see traffic coming in and out from an iOS application, Proxyman provides the possibility to install the Proxyman Root Certificate on an iOS simulator or device.&lt;/p&gt;&lt;p&gt;To install the certificate on a iOS simulator:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Launch an iOS simulator, for example by running the app from Xcode.&lt;/li&gt;&lt;li&gt;Open Proxyman&amp;#x27;s menu &lt;code&gt;Certificate &amp;gt; Install Certificate on iOS &amp;gt; Simulators&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;In the opened &lt;code&gt;iOS Simulators Setup&lt;/code&gt; window, just click install and trust and then reboot the simulators.&lt;/li&gt;&lt;/ol&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1306px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/719612a73ffd47b940501e654592d2eb/15588/20220704_proxyman_ios_simulator.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:58.4%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABfElEQVQoz42SW2+CQBCF+VVVue4ulyJ3sCjSqlG0Nun/fz7NDMGgxaYPJ8Mu7PCdnaM5tgXDMLBYLG6az+es8ZoqfafrOtdBtB72qGovTgDlKlRliTRNkSQJ17IsEUUR8jxHlmUIw/Du4GOjW0PdMGGaJpRSEELAcRx4ngff9yGlhOu6vKY6JhsTjp+1OIvR7t/RNA2rbVumIw10pDHRn5ZF5mJ93uJ87HA4HHA6ndieoRuwTAumYd4q7U1R3hFGeU/YbltsNhsmZMpVCaLPqxzFW4GqrhAn8d3wxhpINZG72H594LO74Hg6ous67HY7qNSFyBVEoSALD6JwoV4VpJB8t4+ybbtvuPRDZFEKP/B5qnRfNBAlJZTsG9zVURMa5K+GwWqJ/fcR18uVrQZBwC9ns1mfx8X8qc1Jy3YokNQpVtWKCS3L4j8OUaEoTU33mbQwCNE2LdbrNYuCPVggUUb/QzhQanEc9xncNKjrminpbijgREeaGsKUyN0P+nyrFauvhboAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;iOS Simulators Setup&quot; title=&quot;iOS Simulators Setup&quot; src=&quot;/static/719612a73ffd47b940501e654592d2eb/15588/20220704_proxyman_ios_simulator.png&quot; srcSet=&quot;/static/719612a73ffd47b940501e654592d2eb/12d18/20220704_proxyman_ios_simulator.png 375w,/static/719612a73ffd47b940501e654592d2eb/44507/20220704_proxyman_ios_simulator.png 750w,/static/719612a73ffd47b940501e654592d2eb/15588/20220704_proxyman_ios_simulator.png 1306w&quot; sizes=&quot;(max-width: 1306px) 100vw, 1306px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;iOS Simulators Setup&lt;/div&gt;&lt;p&gt;Now, networking traffic from the iOS Simulator should show up in the flow list panel.&lt;/p&gt;&lt;p&gt;Since Proxyman installs the certificate only on booted iOS simulators, repeat the process when running the app on a different simulator.&lt;/p&gt;&lt;p&gt;To setup Proxyman with an iOS device, open &lt;code&gt;Certificates &amp;gt; Install Certificate on iOS &amp;gt; Physical Devices&lt;/code&gt;. Proxyman will guide you step by step through the process.&lt;/p&gt;&lt;h3&gt;Limiting results to specific apps or domains&lt;/h3&gt;&lt;p&gt;To limit network traffic showing up in the flow list, for example to only one specific domain, we can use the pin tool.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/5c314f3bc003b9e6d990316ad8fe223c/38fd2/20220704_proxyman_favorites.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:33.33333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABYlAAAWJQFJUiTwAAABfklEQVQoz0XOy27aUBDGcT9B1V0C+IKNDcfHPuf4gqEk4epgiBp1UUW9hLxL1V1WTdQX/lfYUln8NPNpNKOx9CJBZSWx1MjUECeacBwTTWTr3J+NIkG5WlA3R3b3B+r9kfvmgcPDI83xM5vdnk83S6w4VQjZSVR3sMspk1gRJwYhNSIxSJ0jVU6qi/81NWU7i0TasoJwzFVvwHXfpjdwLvoOru+Q6gmFUWQ6IZ5EJPEEKcakUhAGQ4bugMCzCYYda7urWdzcMa3mmCzv5AVGGxwzI1h+QS+/Md2fKOtnZs0L1f6EWj0xzA94psHWDX3Vsc4HqtmcLC9Ile7oDCUlV4uvOE+vqKc/ZD/fMT/eKE9/W/r7W5uL53e8x1c+rH7xcfMbq6zmxIli4Hj0bfdi4OKMbKQJKU3KfJpRFYZ5lbe9SQXxOCARIVHg4rvXBG4Pq/1GZ/ijCMfzLxwfW/io25K72zWrzZblesNmW7Pe1izXW/Ji2u6OhcTzRwyDkH/U/dvymg4SjgAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Pinning a domain&quot; title=&quot;Pinning a domain&quot; src=&quot;/static/5c314f3bc003b9e6d990316ad8fe223c/2e9ed/20220704_proxyman_favorites.png&quot; srcSet=&quot;/static/5c314f3bc003b9e6d990316ad8fe223c/12d18/20220704_proxyman_favorites.png 375w,/static/5c314f3bc003b9e6d990316ad8fe223c/44507/20220704_proxyman_favorites.png 750w,/static/5c314f3bc003b9e6d990316ad8fe223c/2e9ed/20220704_proxyman_favorites.png 1500w,/static/5c314f3bc003b9e6d990316ad8fe223c/38fd2/20220704_proxyman_favorites.png 1530w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Pinning a domain&lt;/div&gt;&lt;p&gt;After selecting the domain or app to pin, drag it into the pin section in the source list panel. Or alternatively right-click and select pin.&lt;/p&gt;&lt;h3&gt;Repeating, editing and composing requests&lt;/h3&gt;&lt;p&gt;To repeat a request without resending it manually, we can use the repeat tool by selecting a request in the flow list, right-clicking it and choosing &lt;code&gt;Repeat&lt;/code&gt;.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:938px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/efe663cc024235e00222cbcce123fe53/a0abd/20220704_proxyman_repeat.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:37.866666666666674%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABWUlEQVQoz1XR6Y6CMBQFYN5D7cKmyNKWoqCAOo6D4hrj+z/LmVAN6o8vN2k4J7TX4mqJtDojyhuIZQtZHuHFFdyo7Dnhsvc8e05P1Bj4GQb+rGfxqUIgFhgnOaZpiUAWyLMK5/0Vp/0VdbFBPFGQYfaiUegSWuTwghTEE6D+m8UYAyEElFIQSjAajZCpArfTA8fmit1PizhIkUy1mTKaIde1mZzb6PKM857FusMPlHLoZIZ2d8Zu3aDdnbDftlCRRjyRSAKFZPJkcxeUcTBmm9mxuOOC22+MOya8rf9QzmuoUBumyJRJc20VZbC7723nK29RyvCJEAoZpfhdNaiKtSmJxgJJIN+FkTYY4yCUfuUt2/VgOy+uZ/4wk3PcTnesFptXkUQ8Eb1M5JBhCsfx0d2wzzserG4hn4bDETIxw/3yQLM94Hzotn0x7xr6sSmfq8IUMsrMEj/z/zTh8peSCn7ZAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Repeat a request&quot; title=&quot;Repeat a request&quot; src=&quot;/static/efe663cc024235e00222cbcce123fe53/a0abd/20220704_proxyman_repeat.png&quot; srcSet=&quot;/static/efe663cc024235e00222cbcce123fe53/12d18/20220704_proxyman_repeat.png 375w,/static/efe663cc024235e00222cbcce123fe53/44507/20220704_proxyman_repeat.png 750w,/static/efe663cc024235e00222cbcce123fe53/a0abd/20220704_proxyman_repeat.png 938w&quot; sizes=&quot;(max-width: 938px) 100vw, 938px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Repeat a request&lt;/div&gt;&lt;p&gt;Alternatively to the &lt;code&gt;Repeat&lt;/code&gt; option, there is an &lt;code&gt;Edit and Repeat&lt;/code&gt; option to edit a request before resending it. It allows to change query, header values and body of the selected request.&lt;/p&gt;&lt;p&gt;We can also compose a fully new request using the compose button in the menu or by selecting &lt;code&gt;Tools &amp;gt; Compose Request&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Using breakpoints to intercept and edit requests and responses&lt;/h3&gt;&lt;p&gt;The breakpoint tool allows us to edit the content of a request or response on the fly.&lt;/p&gt;&lt;p&gt;Right-clicking the request and selecting &lt;code&gt;Tools &amp;gt; Breakpoint&lt;/code&gt; opens the following breakpoints dialog.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6797c49a35494cd5a9f12793761fde8a/6de9d/20220704_proxyman_breakpoint.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:29.333333333333332%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABK0lEQVQY002R207DMAxA+yswtrI1TZpmbbpbu7K20+4DwYYEb7zBf/DbBzWgsYejY8uW7MSe6Ai0L7F6TLH7wM52KDVB6QKlc6KWeE6kC2dtrikvxKZ0dS+VKXObM0tzNpsz6+UTVblj0xxYNweqhz31fEVTrimnNVZPsPHEOVEjhipztLEJLV7kK2RXEvmSQ71i2xypqhOn/TPvL2+Ui1esTJnEI6xMSALj8jRMUN0Q2RVI51882ZNMzYhsOKfIt9R5TVkeWBYN43iM6atLs+pJlK8I7wTB7cAhOgHhbR9x4zt7bWPcj9AD4/5xFGXuOVlkL5u0JGLocqtSlC/R9xGZsgwDgwynaLNw9tpp7WHCToD4m3ixQ1zF/7TbP66PNHlFWH8Sn78R9Rc/NxXDJKbARwoAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Adding a breakpoint&quot; title=&quot;Adding a breakpoint&quot; src=&quot;/static/6797c49a35494cd5a9f12793761fde8a/2e9ed/20220704_proxyman_breakpoint.png&quot; srcSet=&quot;/static/6797c49a35494cd5a9f12793761fde8a/12d18/20220704_proxyman_breakpoint.png 375w,/static/6797c49a35494cd5a9f12793761fde8a/44507/20220704_proxyman_breakpoint.png 750w,/static/6797c49a35494cd5a9f12793761fde8a/2e9ed/20220704_proxyman_breakpoint.png 1500w,/static/6797c49a35494cd5a9f12793761fde8a/6de9d/20220704_proxyman_breakpoint.png 1576w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Adding a breakpoint&lt;/div&gt;&lt;p&gt;It provides different options to configure which requests and responses should be intercepted.&lt;/p&gt;&lt;p&gt;After creating the breakpoint, Proxyman will open a new editing window as soon as it receives a request or response we want to intercept. It allows us to modify the data as we wish before continuing the execution with new changes.&lt;/p&gt;&lt;h3&gt;Using map local tool to change responses&lt;/h3&gt;&lt;p&gt;Being able to change responses is a really useful feature when developing apps, for example to simulate different scenarios or errors and see how our app responds.&lt;/p&gt;&lt;p&gt;The map local tool enables us to setup a custom response by right-clicking the request and selecting &lt;code&gt;Tools &amp;gt; Map Local&lt;/code&gt;.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/a1f457305781686afce95f8d88a8edd9/dc660/20220704_proxyman_map_local.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:73.6%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAABYlAAAWJQFJUiTwAAACkUlEQVQ4y3WS2ZLbNhBF+ReJLYk7AILYuGqXPdJkYqXiqtTkKT+Q//+EkyI0nlQWPxx2A0R1XzRucn/aMu63DLZn8AODHyOjH+ndwHbY8nR+iszdHPce5/6FGxjDRCIbSzfuEP2Bdd2yrvU7G9H+I0+lIVP2u6SVJFGNZne5cv/jT04vX9lfv3B8/oX99f7Ib3fG0w07nbDz93HbM6INJEUlsL7j/uVnLs9febn/xu+vr5xfXumHkWneEroe50OMjW6RskGq/1IJSVLWgrKWuDDgupHQT4R+pDGevKzJiirGvBQszfOFuP5/ktZ1iMaQ5iWbrGCd5m9kVEJxvf3EtN3HW7jQkZdVPLvw3qyoYr6QCDtwvL5wvlzY7Q8432HD44pLgWHa0Q0z3TBF9db3tG7519O0lko01FIjlI4CkkpJ7DjhhhNhPhGGHb1xSDugbYfRDdZoTKtj9M5gbYuzJu5p3VALSVpUlJUgWWQuHeZpZhoDu13PdvTMU8dx9lx2gdPsOE+W4+y4zI7TYPFGEYyKUSuBEG8zXD7aBA6HPYfTge1hT98HxsHjO4sLFhMcrnNMfReVWSnj3PKifCfLi0fBoqqpdUfbf8KOn2n6C0L3ZKVinRbxobKsjPlqnfFxnfHjOmOV5pHlAT9uHg+4zDFZLFOrlkp7pG5RSuFUQ6uaOD+tNaE1iKqiKCvKN9K8YJ09Gq42Gdq4SDT2YoHStgjd4FuBFBWiLiOyfstFFWfVyDrGoixZbXI2Wc4PH1YI1aBbQ1LLhn6c8d1Aow21VFF+uVArilpGaxSVJM0Xr9Wkxd9GXgQZ6+nnI1J7EqUNT7fnyFJ4kW1ciEY2zse1f/ecYhHwjW/ry+UTp9uvlE3PX+OewlREs9g+AAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Changing the response with the map local tool&quot; title=&quot;Changing the response with the map local tool&quot; src=&quot;/static/a1f457305781686afce95f8d88a8edd9/2e9ed/20220704_proxyman_map_local.png&quot; srcSet=&quot;/static/a1f457305781686afce95f8d88a8edd9/12d18/20220704_proxyman_map_local.png 375w,/static/a1f457305781686afce95f8d88a8edd9/44507/20220704_proxyman_map_local.png 750w,/static/a1f457305781686afce95f8d88a8edd9/2e9ed/20220704_proxyman_map_local.png 1500w,/static/a1f457305781686afce95f8d88a8edd9/dc660/20220704_proxyman_map_local.png 1812w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Changing the response with the map local tool&lt;/div&gt;&lt;p&gt;In the opened window, we can edit the response directly or choose a local file that will serve as response.&lt;/p&gt;&lt;h3&gt;Exporting request and response data&lt;/h3&gt;&lt;p&gt;To export or share networking data with others, we can copy a cURL by right-clicking the request and selecting &lt;code&gt;Copy cURL&lt;/code&gt; or use one of the export options by selecting &lt;code&gt;Export&lt;/code&gt;.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:956px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/be5aedfef6c20a54d42f2f18fa762363/04410/20220704_proxyman_export.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:46.666666666666664%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABf0lEQVQoz4WS226jMBRF+xtVwiVJwTbYYALlEu4IkjRpG01H6nNVaf5p+gH9zj2yZzLJU/uwhI+QF2efw818dou76heK90/Qnx+YHX9j/nhB1fbpAyL7gaHq0Ddb9PWokTwGXXF4JIBhLTA3bdzMDQNU1Gh2bwjzFzjhASQ6XpBHsPgJghcokhJV3qEuepRpizJrkK43YC6HeRbODAuCBdi2IzKZIqQCgnBwx4dwFRwBEZD+GvvxiLHbIVuXyJMKTdFjkzagjg/TWl6E3IswDQeM/QOGdo+u3uKwO0GKFMwNQZ0AIY+1cGi2SMIcZOmDLHzQlQ/PFVeRTVsXlr2CaS9hmAv9Ncte6qfCMG14RCCPK5RZq+FEQtAIgkX63X/h+fAVKgV1OTZJg3tZaHEcZrpLtRTqXM3w3OFX6LEwiZfnVzzuT5qH6UmL1TwDL9LJvu3wWsiIQFeNaDeDRm27ryeNFMnfO9Y/4bcdmpb+NaZ+j0FJqklvV0VmdwLsKvIfDZMU5VxrQLAAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Request and response exporting options&quot; title=&quot;Request and response exporting options&quot; src=&quot;/static/be5aedfef6c20a54d42f2f18fa762363/04410/20220704_proxyman_export.png&quot; srcSet=&quot;/static/be5aedfef6c20a54d42f2f18fa762363/12d18/20220704_proxyman_export.png 375w,/static/be5aedfef6c20a54d42f2f18fa762363/44507/20220704_proxyman_export.png 750w,/static/be5aedfef6c20a54d42f2f18fa762363/04410/20220704_proxyman_export.png 956w&quot; sizes=&quot;(max-width: 956px) 100vw, 956px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Request and response exporting options&lt;/div&gt;&lt;p&gt;After choosing a destination, Proxyman will create two &lt;code&gt;.txt&lt;/code&gt; files with all relevant network data for the selected request and response.&lt;/p&gt;&lt;h3&gt;Further reading&lt;/h3&gt;&lt;p&gt;Proxyman offers more features and tools like map remote, saving sessions, GraphQL debugging, simulating different network conditions, scripting and more. Check out their &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://docs.proxyman.io&quot;&gt;official documentation&lt;/a&gt; to find out more.&lt;/p&gt;</content:encoded><category>networking</category><category>tools</category><category>ios</category></item><item><title><![CDATA[Customizing buttons in SwiftUI with the ButtonStyle protocol]]></title><description><![CDATA[Learn how to reuse button styles in SwiftUI.]]></description><link>https://tanaschita.com/swiftui-customizing-button-with-buttonstyle</link><guid isPermaLink="false">https://tanaschita.com/swiftui-customizing-button-with-buttonstyle</guid><pubDate>Sun, 22 May 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SwiftUI provides a &lt;code&gt;ButtonStyle&lt;/code&gt; protocol which allows us to create reusable button styles without having to create new views.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s directly jump into an example.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;When working with buttons, SwiftUI provides modifiers to customize them. Instead of writing:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Primary&amp;quot;, action: someAction)
    .padding(.horizontal, 20)
    .padding(.vertical, 10)
    .foregroundColor(.white)
    .background(Color.red)
    .clipShape(RoundedRectangle(cornerRadius: 10))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;we can encapsulate that style:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct PrimaryButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -&amp;gt; some View {
        configuration.label
            .padding(.horizontal, 20)
            .padding(.vertical, 10)
            .foregroundColor(.white)
            .background(Color.red)
            .clipShape(RoundedRectangle(cornerRadius: 10))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then reuse it cleanly:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;Button(&amp;quot;Primary&amp;quot;, action: someAction)
    .buttonStyle(PrimaryButtonStyle())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;makeBody&lt;/code&gt; function provides a configuration that we can use to adjust the appearance. It also provides the property &lt;code&gt;isPressed&lt;/code&gt; which indicates whether the user is currently pressing the button.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;.foregroundColor(configuration.isPressed ? .brown : .red)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Another benefit we get is that we can set the button style in the environment of a view.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;VStack(spacing: 10) {
    Button(&amp;quot;Primary button 1&amp;quot;) {}
    Button(&amp;quot;Primary button 2&amp;quot;) {}
    Button(&amp;quot;Primary button 3&amp;quot;) {}
}
.buttonStyle(PrimaryButtonStyle())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Setting the button style on the &lt;code&gt;VStack&lt;/code&gt; will automatically apply it to all the buttons inside the view.&lt;/p&gt;</content:encoded><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Customizing the appearance of the NavigationView title in SwiftUI]]></title><description><![CDATA[Learn how to create a custom navigation bar title view in SwiftUI.]]></description><link>https://tanaschita.com/switui-navigationview-customize-title</link><guid isPermaLink="false">https://tanaschita.com/switui-navigationview-customize-title</guid><pubDate>Sun, 15 May 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Note: Starting with iOS 16, &lt;code&gt;NavigationStack&lt;/code&gt; provides more robust navigation handling, such as support for deep linking, programmatic navigation, and improved management of state. While &lt;code&gt;NavigationView&lt;/code&gt; remains available for backward compatibility, it&amp;#x27;s recommended to use &lt;code&gt;NavigationStack&lt;/code&gt; for modern SwiftUI projects.&lt;/p&gt;&lt;p&gt;In this article, we&amp;#x27;ll look at how to customize the navigation title for &lt;code&gt;NavigationView&lt;/code&gt;. Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The default &lt;code&gt;NavigationView&lt;/code&gt; in iOS shows a large title when it&amp;#x27;s expanded and switches to an inline title when scrolled. The following code example results in such default behaviour.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;NavigationView {
    VStack {
        /// List...
    }
    .navigationTitle(&amp;quot;Title&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SwiftUI provides 2 options to customize the appearance of the title:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;the &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt; modifier&lt;/li&gt;&lt;li&gt;the &lt;code&gt;toolbar(content:)&lt;/code&gt; modifier.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;The &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt; modifier&lt;/h3&gt;&lt;p&gt;By using the &lt;code&gt;navigationBarTitleDisplayMode(_:)&lt;/code&gt; modifier and setting it to &lt;code&gt;.inline&lt;/code&gt; disables the large title style. The title always stays within the standard bounds of the navigation bar.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    NavigationView {
        VStack {
            // List...
        }
        .navigationTitle(&amp;quot;Title&amp;quot;)
        .navigationBarTitleDisplayMode(.inline)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;The &lt;code&gt;toolbar(content:)&lt;/code&gt; modifier&lt;/h3&gt;&lt;p&gt;In case we need a custom appearance of the title, we can use the &lt;code&gt;toolbar(content:)&lt;/code&gt; modifier.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;var body: some View {
    NavigationView {
        VStack {
            // List...
        }
        .navigationTitle(&amp;quot;Title&amp;quot;)
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .principal) {
                Image(systemName: &amp;quot;photo.fill&amp;quot;)
                    .foregroundColor(.green)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We set a &lt;code&gt;ToolbarItem&lt;/code&gt; of placement type &lt;code&gt;.principal&lt;/code&gt; with the content we want to show as a title view. If a navigation bar title is also configured, it gets overridden by the toolbar item.&lt;/p&gt;&lt;p&gt;We can replace the &lt;code&gt;Image&lt;/code&gt;view from the example above with any type of SwiftUI view hierarchy.&lt;/p&gt;</content:encoded><category>navigation</category><category>swiftui</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Quick guide on local notifications for iOS]]></title><description><![CDATA[Learn how to schedule and handle local notifications using the UserNotifications framework.]]></description><link>https://tanaschita.com/ios-local-notifications-guide</link><guid isPermaLink="false">https://tanaschita.com/ios-local-notifications-guide</guid><pubDate>Sun, 01 May 2022 22:00:00 GMT</pubDate><content:encoded>&lt;p&gt;iOS local and push notifications allow us to keep users informed about important updates, reminders, or time-sensitive events, even when the app is running in the background or is inactive.&lt;/p&gt;&lt;p&gt;While remote push notifications are sent from a server via the Apple Push Notification Service (APNs), local notifications are generated and controlled by the app itself. Both are configured using the &lt;code&gt;UserNotifications&lt;/code&gt; framework.&lt;/p&gt;&lt;p&gt;In this guide, we are going to look at how local notifications work, how to ask for user&amp;#x27;s permission, how to schedule them in different ways, and how to handle them effectively.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s jump in.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Requesting user permission&lt;/h3&gt;&lt;p&gt;Before sending notifications, we need to request the user&amp;#x27;s permission, typically when the app starts or when a user first accesses a feature that triggers notifications.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;import UserNotifications

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
    if granted {
        print(&amp;quot;Notification permission granted.&amp;quot;)
    } else if let error = error {
        print(&amp;quot;Error requesting notification permissions: \(error.localizedDescription)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With &lt;code&gt;[.alert, .badge, .sound]&lt;/code&gt;, we ask for the ability to display alerts, to update the app&amp;#x27;s badge and to play sounds.&lt;/p&gt;&lt;p&gt;There are more options available. For example, we could ask for:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;criticalAlert&lt;/code&gt; - which requires a special entitlement issued by Apple because they bypass silent modes.&lt;/li&gt;&lt;li&gt;&lt;code&gt;.carPlay&lt;/code&gt; - to display notifications in a CarPlay environment.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Creating notification content&lt;/h3&gt;&lt;p&gt;Once we have permission, the next step is to create the notification&amp;#x27;s content using &lt;code&gt;UNMutableNotificationContent&lt;/code&gt;. For example:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let content = UNMutableNotificationContent()
content.title = &amp;quot;Stay hydrated&amp;quot;
content.body = &amp;quot;It&amp;#x27;s time for a glass of water&amp;quot;
content.sound = UNNotificationSound(named: UNNotificationSoundName(&amp;quot;water.wav&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the above example, we set the &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt; and &lt;code&gt;sound&lt;/code&gt; properties. For a native sound, we could just set it to &lt;code&gt;.default&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;There are more options available. For example, the &lt;code&gt;badge&lt;/code&gt; property that specifies the number to apply to the app&amp;#x27;s icon when the notification arrives. Or the &lt;code&gt;threadIdentifier&lt;/code&gt; property to group notifications visually. We could also set the &lt;code&gt;userInfo&lt;/code&gt; dictionary to add some custom data we can use later when handling the notification.&lt;/p&gt;&lt;h3&gt;Scheduling the local notification&lt;/h3&gt;&lt;p&gt;The next step is to define a trigger for the notification. There are three main types:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Time Interval trigger&lt;/strong&gt; which fires after a set number of seconds&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Calendar trigger&lt;/strong&gt; which fires at a specific date and time&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Location trigger&lt;/strong&gt; which fires when the user enters or exits a geographic region&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;To learn how to configure any of those triggers, checkout this article on &lt;a target=&quot;_blank&quot; href=&quot;/ios-local-notification-triggers/&quot;&gt;scheduling notifications with time, calendar, and location triggers&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;Registering the notification request&lt;/h3&gt;&lt;p&gt;Once the content and trigger are ready, we can register a notification:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let id = UUID().uuidString
let request = UNNotificationRequest(
    identifier: id, content: content, trigger: trigger)

UNUserNotificationCenter.current().add(request) { error in
    if let error = error {
        print(&amp;quot;Error adding notification request: \(error.localizedDescription)&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;identifier&lt;/code&gt; allows us to modify or cancel this specific notification later if needed.&lt;/p&gt;&lt;p&gt;The method above is also available as an asynchronous method &lt;code&gt;func add(_ request: UNNotificationRequest) async throws&lt;/code&gt; that we can use when working with &lt;code&gt;async/await&lt;/code&gt; instead.&lt;/p&gt;&lt;h3&gt;Cancelling a notification request&lt;/h3&gt;&lt;p&gt;To cancel a specific request, we pass the id we used when creating it.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [id])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To remove all pending requests, the notification center provides the method &lt;code&gt;removeAllPendingNotificationRequests()&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Handling notifications in the foreground&lt;/h3&gt;&lt;p&gt;When the app is running in the foreground, the notification will not be shown to the user by default. To change this behavior, we can implement the &lt;code&gt;UNUserNotificationCenterDelegate&lt;/code&gt; methods which notify us when the notification is about to be presented.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&amp;gt; Bool {

    UNUserNotificationCenter.current().delegate = self
    return true
}

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -&amp;gt; Void) {
    completionHandler([.alert, .badge, .sound])
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Calling the &lt;code&gt;completionHandler&lt;/code&gt; will show the notification based on the &lt;code&gt;UNNotificationPresentationOptions&lt;/code&gt; parameter we pass in indicating how to present a notification in a foreground app.&lt;/p&gt;&lt;p&gt;Instead of showing the notification, we could also implement a custom solution for example to show an app dialog that makes more sense for this specific use case.&lt;/p&gt;&lt;h3&gt;Handling notification actions&lt;/h3&gt;&lt;p&gt;When the user interacts with a delivered notification, we can handle the response:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -&amp;gt; Void) {

    let userInfo = response.notification.request.content.userInfo
    // Handle notification

    completionHandler()
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we could for example use the &lt;code&gt;userInfo&lt;/code&gt; to trigger certain flows or to show information or screens related to the notification.&lt;/p&gt;</content:encoded><category>notifications</category><category>swift</category><category>ios</category></item><item><title><![CDATA[Manipulating networking requests and responses with Charles]]></title><description><![CDATA[Learn how to use Charles breakpoints tool to change network data and test your iOS application.]]></description><link>https://tanaschita.com/tools-charles-proxy-manipulating-network-requests-and-responses</link><guid isPermaLink="false">https://tanaschita.com/tools-charles-proxy-manipulating-network-requests-and-responses</guid><pubDate>Sun, 06 Mar 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Charles is a proxy server that enables us to view the networking traffic between the app and the internet.&lt;/p&gt;&lt;p&gt;In &lt;a href=&quot;/20220228-quick-guide-on-charles-proxy-for-ios&quot;&gt;Quick guide on Charles Proxy for iOS development&lt;/a&gt;, we already looked at the features Charles has to offer and how to use them.&lt;/p&gt;&lt;p&gt;In this article, we are going to dive more deeper and look at the Charles breakpoints tool. It lets us intercept and manipulate requests and responses before they are passed through Charles.&lt;/p&gt;&lt;p&gt;Being able to manipulate response data is a great way to test how the app handles unexpected values. Let&amp;#x27;s get started.&lt;/p&gt;&lt;h3&gt;Setting up a breakpoint&lt;/h3&gt;&lt;p&gt;Each breakpoint in Charles matches a URL using location matching patterns and is also assigned to either the request, the response or both.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;The simplest way to enable a breakpoint is by selecting the URL and then clicking the breakpoint button in the top toolbar.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1012px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/306322b9eefd7ddf02e01926d89b6760/a6d44/20220307_charles_enable_breakpoints.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:50.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB/0lEQVQoz42S227TQBCG/YigwgUvwBWvwENQEFwibrluJSpVpShtYzU+H3OO4yQVaRwnUew4ceJ8aLe9BbHSp92dHc38M7OK71yiBjrNsEngu4SBQxCEtDyfMAgInnFdF1VVub29Q2s0aOgGdVWlpt7jeT5e4Es/RdctDE1HN0wc8w7PrmHaLsFNHauhoRsNNE2jVruhXq/z8/oXnutwozsYtsOl5qOZFnX7Hsu2UC4ufnB+fk6tVsO2Xe4bGr7vE41iBlFEFEUMh0Ourq44OzvDMAz5HvierKDbaRH4PpZpYZomSncyw/MDut0uwzhmuVxSFFvKXclOUJaUZUkYhrRaLcbjMf1+n16//5wsxnEceRYo2eGIOUlI5zP2u4K/raIoZI82m428Hw4HiRAgVFuWJZMp603FcFVx3Z2j6i7j6ZJVAWl2IM0qySKvWGc5tm3RbDaJ45jZbEZVVaRpKm2LxYLj8Yhy8jHh5DTh9ZeU99+nvPuW8PbrghcfEl59Snh5mvDm85zo95p2M5B9zbKMPM/Z7/dSYafTkUlWqxVKtq0QbLYV2z3s9rDZPdmeOD7teS579/j4KAMKNULher2m3W4zGAx4eHhA4b/WkbLcSQVikmLKIvh0OmU+nzMajWRgMTxFZPo3SCXCWZQnfoNADEDYhFoRsNfrMZlM+AM4jtd1+SNy5wAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Enable breakpoints.&quot; title=&quot;Enable breakpoints.&quot; src=&quot;/static/306322b9eefd7ddf02e01926d89b6760/a6d44/20220307_charles_enable_breakpoints.png&quot; srcSet=&quot;/static/306322b9eefd7ddf02e01926d89b6760/12d18/20220307_charles_enable_breakpoints.png 375w,/static/306322b9eefd7ddf02e01926d89b6760/44507/20220307_charles_enable_breakpoints.png 750w,/static/306322b9eefd7ddf02e01926d89b6760/a6d44/20220307_charles_enable_breakpoints.png 1012w&quot; sizes=&quot;(max-width: 1012px) 100vw, 1012px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Enable breakpoints.&lt;/div&gt;&lt;p&gt;For more control, open &lt;code&gt;Proxy -&amp;gt; Breakpoint Settings&lt;/code&gt; in the menu. Here, we see an overview of all the breakpoints that are set, can edit, remove them or add new ones.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1262px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/8fb0a4eff444b8b53bdd8f3294d971c3/81cec/20220307_charles_breakpoint_settings.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:54.93333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB2UlEQVQoz3WSS2/TQBRG/bMRErBiw6qioiD2SAjoDqpCgwQUokilXfSRxnbSvNw0sedhj0Mcu5UOmmmSRiosjs4dfffeGcn25t0eXdFhlmmqLKcyxnGdT5kKSafZpB+EDIKQnh8wU5rK5I4yMxRaU+jUYTMvGg4JgoBOu+MIwzZBENJud/B932XWYRjeZWGbVsun1+sjpUIkAiEEUgi8k27C6bnPRXBG0DqlEzRJxhEyHjmLyaVDxpfo5AotxigxdpmtZ7nij5HMjEQridfsSWpHEbu/zvl+1Kd+esVvP1kgnA9aCfWzhP2TMbVGyLfDHoeBoNGM2T+esH8y4cfxmO4wxts7Vjz9lLO5q3ixZ9j8kvPqq+Fl7Y7nnzMeb6c82dZs7Ag2dhRbNcOzjxkP32sefdA8eKdpnAu8YmqAa+Bm4Wrh/3Gz4H7f1KR4aZpRzCuKeblivkax5tlaz79QWuNlWUZZ2qH5GnfnZWZdltVaXq7qWXF7mbYL3eeWkqWXtSVNU0ajEfZS6yiKUEqtcvvLKCkoTUJlEld7tiGOY9dolyVJsvJy2Nqel5nDZsmE4UTz9tDw5iAnmsjbhYPBwA3axfdfcf/1t5ZEgz4XI8Xr+pStn1P6V5K/gQ0vL0hpU94AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Breakpoint settings.&quot; title=&quot;Breakpoint settings.&quot; src=&quot;/static/8fb0a4eff444b8b53bdd8f3294d971c3/81cec/20220307_charles_breakpoint_settings.png&quot; srcSet=&quot;/static/8fb0a4eff444b8b53bdd8f3294d971c3/12d18/20220307_charles_breakpoint_settings.png 375w,/static/8fb0a4eff444b8b53bdd8f3294d971c3/44507/20220307_charles_breakpoint_settings.png 750w,/static/8fb0a4eff444b8b53bdd8f3294d971c3/81cec/20220307_charles_breakpoint_settings.png 1262w&quot; sizes=&quot;(max-width: 1262px) 100vw, 1262px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Breakpoint settings.&lt;/div&gt;&lt;p&gt;When editing or adding a breakpoint, we are able to configure details like protocol, host, port, path patterns or wildcards. Here, we can also select whether the breakpoint should be assigned to the request, the response or both.&lt;/p&gt;&lt;h3&gt;Editing request and response data&lt;/h3&gt;&lt;p&gt;When a request hits a breakpoint, the breakpoints window automatically opens in Charles we can view and edit the data.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1a08e82e2765d88f4591ea1bd80045f5/7e042/20220307_charles_edit_requests.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:45.599999999999994%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABYlAAAWJQFJUiTwAAABcElEQVQoz4WSbW+CMBSF+f9/Sc0G6IYoryotbQGJYIzug4mZCqhnuXX6bfMmT85Nc3PbnB5DeDYC4UMJhVSkEEIgTe+6SBJMx2N4noex68JxPpEkCRhnSDgH4wKMMXDONYxxGKMPByNrCNu2YZom+v0+er0eRkMbpmXBtYeYOGMEvocgCPRyL/TxNjTxbtrw/QBhGGriKIBRCol5HGM+n2M2myGKIsRxjFQIrFYrSCURhhFcP9aXEovFAlJIZEphuVwiz3PkeYZEFjDwor52O2RFiQmvIaVElikopVDXNfb7vWaz2WBdrTCVOxi32w1/QXU6nXA4HNCcvtE0Dc7ns4bOj8ejhnqia46vF3Zdh8vlgq67oG3bf2na9r7wer0+eSyjnmq73WrP7j7l2teqqp5alqX28cFLD2loMBjoX3zEg7xUvx9CC4uiQJZlGoPyRkOUPVI6XK/X2nRSehVlj155zxrTs495x3FgWZaOHOkPy8aTnIXJwbMAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Edit request in Charles.&quot; title=&quot;Edit request in Charles.&quot; src=&quot;/static/1a08e82e2765d88f4591ea1bd80045f5/2e9ed/20220307_charles_edit_requests.png&quot; srcSet=&quot;/static/1a08e82e2765d88f4591ea1bd80045f5/12d18/20220307_charles_edit_requests.png 375w,/static/1a08e82e2765d88f4591ea1bd80045f5/44507/20220307_charles_edit_requests.png 750w,/static/1a08e82e2765d88f4591ea1bd80045f5/2e9ed/20220307_charles_edit_requests.png 1500w,/static/1a08e82e2765d88f4591ea1bd80045f5/7e042/20220307_charles_edit_requests.png 1686w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Edit request.&lt;/div&gt;&lt;p&gt;The same happens when a response hits a breakpoint.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/fe864466bf1dcd56a5ddebf8d755690d/9b427/20220307_charles_edit_response.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:38.13333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABTElEQVQoz53Sa2+iQBSAYf7/XzKmRWwQ1y6gXIRFWmurglW7Gy9QmHmbmV42a7L7YU/y5Mz1ZCY5hpe4pMkPkiT5EkWRzp7rcusMGY1G2LbN2PdJ0oRJHBJNE6bTKXEc/8GwTAvTNOn1enS7XTqdjh73+xZWz2LQv8EZqqK3OI6D7QywvzlcXZvYgwGe5/Hddb8YeZaR5zmz2Ywsy7T1ek1Zlszn98zv73h8mHM3y4ijgCgMeFo8UK6XlMWK503Bc1m8580Gg3+EaCrEYUt7fEGef0JzhraC1xOyPiBfT4jzr3fVAdnUGEIILkkpaZqGtm2REoSQH3sSIRWQoPd05vfcUJcvqVDF/if++mX1ouVyqa1WK4IgIAxDFouFXiuKQtvv92y3W3a7nWZUVcWluq45Ho9MJhN839dc12U8Hut2Uu2RpqnOqsXUuU9v7+RTXkHxdgEAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Edit response.&quot; title=&quot;Edit response.&quot; src=&quot;/static/fe864466bf1dcd56a5ddebf8d755690d/2e9ed/20220307_charles_edit_response.png&quot; srcSet=&quot;/static/fe864466bf1dcd56a5ddebf8d755690d/12d18/20220307_charles_edit_response.png 375w,/static/fe864466bf1dcd56a5ddebf8d755690d/44507/20220307_charles_edit_response.png 750w,/static/fe864466bf1dcd56a5ddebf8d755690d/2e9ed/20220307_charles_edit_response.png 1500w,/static/fe864466bf1dcd56a5ddebf8d755690d/9b427/20220307_charles_edit_response.png 1894w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Edit response.&lt;/div&gt;&lt;p&gt;After editing, we can use different action buttons on how Charles should proceed with the request or response. The following actions are available:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;: Applies any changes we made and lets the request or response proceed.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Abort&lt;/strong&gt;: Blocks the request or response and sends an error message to the client.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cancel&lt;/strong&gt;: Discards any changes we made and lets the request or response proceed as originally received.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;Charles breakpoint tool is an easy to use and powerful way to debug our applications.&lt;/p&gt;&lt;p&gt;Charles offers even more advanced tools like repeating, automatic rewrites, map local, map remote, cache, cookies control and more. Check out their &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.charlesproxy.com/documentation/tools/&quot;&gt;official tools documentation&lt;/a&gt; to check those out.&lt;/p&gt;</content:encoded><category>networking</category><category>tools</category><category>ios</category></item><item><title><![CDATA[Quick guide on Charles Proxy for iOS development]]></title><description><![CDATA[Learn how to use Charles to inspect network traffic for your iOS application.]]></description><link>https://tanaschita.com/tools-charles-proxy-for-ios-guide</link><guid isPermaLink="false">https://tanaschita.com/tools-charles-proxy-for-ios-guide</guid><pubDate>Sun, 27 Feb 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Charles is a proxy server that enables us to view the networking traffic between our iOS app and the internet.&lt;/p&gt;&lt;p&gt;We can configure the iOS simulator or device to pass its networking requests and responses through Charles Proxy, so we’ll be able to inspect and even change the data in our testing and debugging process.&lt;/p&gt;&lt;h3&gt;Recap on proxies&lt;/h3&gt;&lt;p&gt;A proxy is an application that acts as an intermediary between a client requesting a resource and the server providing that resource. So instead of connecting directly to a server, the client directs the request to the proxy, which performs the request, receives the response and then directs the response to the client.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1324px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:15.466666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAgUlEQVQI142OSwqDMABEvf/ZlAbjASxBFNOkRhOM31cIbSmli77NLIZ5TMaT8zxTHttGMJZgLfuyEJ3DG0OcJvYYCca8u8/di+xbGIcBJSXXsqTPc1ohqKWkuQhuRYGqKmoh8L3+T3isK2Pb4pqG1fv08K4UQev0cOy61G3z/FP4ANum5zHXertVAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Proxy as interactor between client and server.&quot; title=&quot;Proxy as interactor between client and server.&quot; src=&quot;/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png&quot; srcSet=&quot;/static/70e51500a59e00281577c96a511566e3/12d18/20220228_proxy.png 375w,/static/70e51500a59e00281577c96a511566e3/44507/20220228_proxy.png 750w,/static/70e51500a59e00281577c96a511566e3/305cf/20220228_proxy.png 1324w&quot; sizes=&quot;(max-width: 1324px) 100vw, 1324px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Proxy as interactor between client and server.&lt;/div&gt;&lt;p&gt;When communicating over HTTPS, encryption prevents proxy servers and other middleware from eavesdropping on sensitive data. SSL/TLS encrypts the networking traffic using certificates generated by trusted certificate issuers.&lt;/p&gt;&lt;p&gt;For Charles to be able to decrypt and show us encrypted traffic, it offers to generate its own certificate that we install on our iOS simulator or device and explicitly trust it. We&amp;#x27;ll look at that in more detail later.&lt;/p&gt;&lt;h3&gt;Getting started with Charles&lt;/h3&gt;&lt;p&gt;Charles Proxy isn’t free, but offers a free 30-day trial that can be downloaded on the &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.charlesproxy.com/download/&quot;&gt;official Charles download page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;After installing and launching the app, Charles should ask for permission to automatically configure our network settings. When granted, Charles will change our computer&amp;#x27;s network configuration to route all traffic through it. This allows Charles to inspect all network events to and from our computer.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;After granting privileges, Charles directly starts recording network traffic that we see on the left pane.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ced6d560485c0ae574ff1ebc140d04a8/abd3c/20220228_charles_start_screen.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:53.6%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAABYlAAAWJQFJUiTwAAABb0lEQVQoz42SSW/CQAyF8/9/QqUipJ57q7rcu9DAgZLJUEJWEsqSTewBknmVRw0KNJVq6dNElufZzxmFa88wBgy2ocM1OdwhgzvsYTRgsAwGVVXRarXw8t5Bp9uFxjV0tR56GgNjOnT9HOXqto3m3Qea9xzXjxYaEhONBws3TwO8vqnQGQPvf0LnfViWBdfz4P2B0jPnUFmAdn+CwVcGJyxgz3NY8wJelMMPxsiyDEmSwLZtjMdjxFGENElkjkjTVJ5RFEEBBIDih99BXYMgwGazwWKxwH6/x263q2W9XkMRAigphDiDIgxDOI4jL5DY4XCo5Xg8yhpFCIEqFNVvsjMajbDdbqV1ukzCl1BeCtbZrApOp1O4rovlcnkmWkfthJfT0qJns5ksjuMYq9WqdkKCGkrBoigkdYL092iHVEzQrkqL/7ZcjXKHNClNSI3yPK+FQjEMQz5Wwvd9TCaTE+X+TNM8nZSr1pTQ0+Kc4xu/AkFDOkeYbgAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Charles start screen.&quot; title=&quot;Charles start screen.&quot; src=&quot;/static/ced6d560485c0ae574ff1ebc140d04a8/2e9ed/20220228_charles_start_screen.png&quot; srcSet=&quot;/static/ced6d560485c0ae574ff1ebc140d04a8/12d18/20220228_charles_start_screen.png 375w,/static/ced6d560485c0ae574ff1ebc140d04a8/44507/20220228_charles_start_screen.png 750w,/static/ced6d560485c0ae574ff1ebc140d04a8/2e9ed/20220228_charles_start_screen.png 1500w,/static/ced6d560485c0ae574ff1ebc140d04a8/abd3c/20220228_charles_start_screen.png 2224w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Charles start screen.&lt;/div&gt;&lt;p&gt;When calling some URL for example in a browser, we should see the requests pop up in the left pane of Charles.&lt;/p&gt;&lt;p&gt;To be able to see the traffic coming in and out from an iOS simulator, follow these steps:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Quit the iOS Simulator&lt;/li&gt;&lt;li&gt;Launch Charles&lt;/li&gt;&lt;li&gt;Open menu &lt;code&gt;Help &amp;gt; SSL Proxying &amp;gt; Install Charles Root Certificate in iOS Simulators&lt;/code&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;This will install the Charles Root Certificate into all iOS Simulators. Now, we should also be able to see the networking traffic from the iOS Simulator in Charles.&lt;/p&gt;&lt;p&gt;To setup Charles with an iOS device, check out &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/&quot;&gt;the official documentation&lt;/a&gt;. Charles also offers a &lt;em&gt;Charles for iOS&lt;/em&gt; app that can be used instead to monitor networking traffic for iOS devices.&lt;/p&gt;&lt;h3&gt;Charles sessions&lt;/h3&gt;&lt;p&gt;When we start Charles, a new session is automatically created by default and contains all recorded information. In most cases, we&amp;#x27;ll only need one session. Sessions can be saved and re-opened for example for sharing with others.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:776px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/d9edcfbdead36abeaecd9520f097c679/97f2a/20220228_charles_session.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:11.733333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAYAAABYBvyLAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAeElEQVQI1x3Haw6DIBBFYfe/pBq0L9KqFZVhTGRkLaeRHzfnu42ZkXOuK6VwHAfOOTrn8N7TdT3L/ONx72nbG/71JIpQ7MSykS1z2ln/5UZEUN1RVUIIDMNYfTVGYU/Cd0mMq7DExHvd0SRMElhlQyUxbjOfbar+A3AqiYdg80wWAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Charles default session&quot; title=&quot;Charles default session&quot; src=&quot;/static/d9edcfbdead36abeaecd9520f097c679/97f2a/20220228_charles_session.png&quot; srcSet=&quot;/static/d9edcfbdead36abeaecd9520f097c679/12d18/20220228_charles_session.png 375w,/static/d9edcfbdead36abeaecd9520f097c679/44507/20220228_charles_session.png 750w,/static/d9edcfbdead36abeaecd9520f097c679/97f2a/20220228_charles_session.png 776w&quot; sizes=&quot;(max-width: 776px) 100vw, 776px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Charles default session.&lt;/div&gt;&lt;h3&gt;Recording&lt;/h3&gt;&lt;p&gt;Charles records requests and responses to the current session for us to inspect. We can start and stop recording by using the record button in the top toolbar.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:330px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/b8674b766a514d8a824344a1a8908f7f/704d3/20220228_charles_recording.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:47.878787878787875%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABwklEQVQoz4VSya4aMRD0L+cQ5RRxy5c8FOkRAQIkEELsY8aezWPPKgQRmwRi+YEcKmoH5im55FCyu7t6KbtZURQo8hz2LAqUeY7yef8f8pyQ/wWWpikIxhjEcYwwT6GMhtEar1iaJEiMQZIkH74nyrJAlmVVjGmtQRBCwJMSp4GDhEtw4doGf+IGJt3AmBRav3wacWwwHk/heZ4diHwsiiKoKILDOZQf4tenb/jZG2HhCURBAKVi+L6A4D8g5RxKaURRiDhWkNLH5y9f8fb2HVmWIgxDMKpOWCzmmE6nEJxjMZ9jNptBSgnP8yGEC+6MIQS39iuH4qTMqnv6GMkiKKXspEEYVsTVagXO+TPJw2rlWp/ruhWoSBAEsEqVAut0Ouh2u6Cz3W6j8f5uiYfDAZfLBff7HbvdDsfjEbfbDdfrFafTCfv9Ho/Ho7oTf7PZgDUaDVuw3+/borVaDc1m03Z2HAe+79sfHI1G9o1oiuVyaZtOJhOUZWkbkt1qtcB6vR4GgwGGw6FFvV63RCLRXtHvrdfrjz0tS2tvt9vqt4lHq0OyGY1NsggkiU4an5KJRPh39+iN6S2pGMk9n882l2r9BrYfrDISENcLAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Charles record button&quot; title=&quot;Charles record button&quot; src=&quot;/static/b8674b766a514d8a824344a1a8908f7f/704d3/20220228_charles_recording.png&quot; srcSet=&quot;/static/b8674b766a514d8a824344a1a8908f7f/704d3/20220228_charles_recording.png 330w&quot; sizes=&quot;(max-width: 330px) 100vw, 330px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Charles record button.&lt;/div&gt;&lt;p&gt;By default, Charles tracks all network calls. We can control the recorded traffic by opening &lt;code&gt;Proxy -&amp;gt; Recording Settings&lt;/code&gt; in Charles menu and adding the API endpoints we want to observe. Port 443 is the standard port for HTTPS traffic.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1178px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/f405fa40e9ec0c26178dd381a732df8e/e2368/20220228_charles_recording_settings.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:68%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAABrUlEQVQ4y6WTS3OiQBSF+e3zK2Y7f2EWWc3KZ1k1Wr7QJFgQBUTeBpECXJypc7UpLLOYJIuvuvv2uacvfRvN9kx4sYPqmOGcHlHcqLITfNuG+fyCt1dDRq4ZL1q6NszXoihCGIYPbLdbmJaFIAjg+75gmqbEGXvIiSLQS/M87y6Jc2IYBlarFbhv2zZc18V6vcZms2k03FPs9/urIQWdTgfD4RD9fh+9Xg/dbhej0UhgjGvGB4OBoDSz2Qzz+RzT6VQOFMPdbgd9ucB4PMZkMsFyuZTKdF2XuYLrxWLRjNTQxHEcoanQPQT4uyvx8ryWT7IsSz6Rd0VBXdcoy/JD8jzH+XyW8XA4yF1qfhDgVFS4XC6SXFXVHYz9D6pRGid1VT4YfQZWe29Y11LhV7mrkE+Fd/Bd6COG7U4p2BSeyM5xzeZQx4tXsUbruNjaHG9dVg+aj7M9Z7Jac97m+jMw7uMYeiiSPfLEuxpSwNOzLJPTKU7TVDbjOH6Ace47jos09PCkn/DjT4GfgxxRHF+bQlOKVFVJkjSG0e0fbcN9jzmRj6Hxjl+jE37P3qXCf38DFCwH3GqGAAAAAElFTkSuQmCC&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Charles recording settings&quot; title=&quot;Charles recording settings&quot; src=&quot;/static/f405fa40e9ec0c26178dd381a732df8e/e2368/20220228_charles_recording_settings.png&quot; srcSet=&quot;/static/f405fa40e9ec0c26178dd381a732df8e/12d18/20220228_charles_recording_settings.png 375w,/static/f405fa40e9ec0c26178dd381a732df8e/44507/20220228_charles_recording_settings.png 750w,/static/f405fa40e9ec0c26178dd381a732df8e/e2368/20220228_charles_recording_settings.png 1178w&quot; sizes=&quot;(max-width: 1178px) 100vw, 1178px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Charles recording settings.&lt;/div&gt;&lt;p&gt;To be able to see the encrypted traffic, we also need to add the same endpoints to &lt;code&gt;Proxy -&amp;gt; SSL Proxying Settings&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Structure and sequence view&lt;/h3&gt;&lt;p&gt;In the left pane of the Charles app, we can switch between the structure and the sequence view.&lt;/p&gt;&lt;p&gt;The structure view lets us view the requests in a tree organised by the host name and the directories within the host.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:560px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6077cdd4128d82b5c51e2253e20e6849/360ab/20220228_charles_structure.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:71.73333333333333%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAAB6klEQVQ4y52SzWvTYADG8x95qHj0VFDw4GUwEGV41JueRJSNKYqiDNSDyAQZogPZ/MAeREFn59iKaJe2SdrS2tktNGubpLVN27R5f9K0g3ab2vnCj7w8PHne5/2QVlbXWP6yQji8zOcB1uUY8YSComooapKEovVR/4BGPK4gvV18xquFeUJvFgm9fsG70Euez82ixb+R0db59CGE/DXM99WPpGIRauYWtfLmEL/Km9TNLSxjA+nolRgXHqtcfppm7JbK+B2VsdsKE/ezjM/84NRMmuB0iuC1tM+xG9k9HL+ZJXg9y+l7GaTA5DYX5ytcWqhy8oHN+bkqZ2dtzj2xOfPQZOKRzZHpEoGpIoenigQm99LVD10tcuKugYQ/OtRcl/emR0aTqRg/+b/RRmp7Ak8IOp6g0WyxtN1mbaNIPt27iHLZ8q1u26Pr3ZdO939oNF0khEAAQggQHrrTQSnViGR1EokEuVwOx3H8UN/T/+7MB3XXdZFET+mZfNmj1PRYKnnk83kikQi6rg8F/T1wp2E/2OtNfJotl0qliqIoRKNRf4F/NhwUxYC5e651p4ll2aRSKVRVxTCM0QOHgvvUnQamafkNZVk+WMPdgd3tO40Wlm2TTCb9hoVC4eCBu02j6KM13OdWR3k2vwH7GAWW+TsskwAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Structure view in Charles&quot; title=&quot;Structure view in Charles&quot; src=&quot;/static/6077cdd4128d82b5c51e2253e20e6849/360ab/20220228_charles_structure.png&quot; srcSet=&quot;/static/6077cdd4128d82b5c51e2253e20e6849/12d18/20220228_charles_structure.png 375w,/static/6077cdd4128d82b5c51e2253e20e6849/360ab/20220228_charles_structure.png 560w&quot; sizes=&quot;(max-width: 560px) 100vw, 560px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Structure view in Charles.&lt;/div&gt;&lt;p&gt;The sequence view lets us view the requests in the sequence that they occur.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1472px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/1a0fc248dcc1583a9f66a56ee772bc30/d2b1d/20220228_charles_sequence.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:17.066666666666666%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA00lEQVQI1x2LXUvCUABA9+ubGQ3xtT/Qk3Nu0UMjLFRM61JShjHdLvPOXDiX+VE6PMF9OA+HwzFaIuC6NaR288jlVZe6L2gPFM2niM4gpv/+SW84o/uacCciOi8SvxdwL0LtD2+K9rPktj+hKSTGhZ9i2jNKdsKpm1FxFWfegrIzp+JIqp6i6k2xvJSS88W5HWHWU6xGwklNUbZjLCfWzXSXGOtVzmaVaSh2JHHI4feH3TonDEbIyQfheES+mFP8bYijgONhq/07S5nKsX6O+y1Q8A+Pwc6MD/jJBQAAAABJRU5ErkJggg==&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Sequence view in Charles&quot; title=&quot;Sequence view in Charles&quot; src=&quot;/static/1a0fc248dcc1583a9f66a56ee772bc30/d2b1d/20220228_charles_sequence.png&quot; srcSet=&quot;/static/1a0fc248dcc1583a9f66a56ee772bc30/12d18/20220228_charles_sequence.png 375w,/static/1a0fc248dcc1583a9f66a56ee772bc30/44507/20220228_charles_sequence.png 750w,/static/1a0fc248dcc1583a9f66a56ee772bc30/d2b1d/20220228_charles_sequence.png 1472w&quot; sizes=&quot;(max-width: 1472px) 100vw, 1472px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Sequence view in Charles.&lt;/div&gt;&lt;p&gt;Note: If you don&amp;#x27;t see any requests in the sequence view, uncheck the &lt;code&gt;Focused&lt;/code&gt; checkmark or mark the endpoints you want to see as focused by right-clicking them in the structure view.&lt;/p&gt;&lt;h3&gt;Request and response views&lt;/h3&gt;&lt;p&gt;When choosing one request in the left pain, we get a detail view on the right side.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1500px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/ecfac156311009587988bd51d8c15dc1/b5233/20220228_charles_request_response.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:48.53333333333334%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAABYlAAAWJQFJUiTwAAABeUlEQVQoz42S6W6DMBCEef+3qlTRNrQB2+Q+aSgGckfkhkw1RkZqftXSJ1/r3dldO1+fL5ChhJISYRgaOp0OpJQQog0V+FBCwg98uO4b3lstvH54kEoiUAKBVFBKNW8d7yuAFAJRFCHLMsRxbAxIt9vFdzTHTxxjsViYuyzLodMMOtVIdI3WKdK0xsmKCxZpDjvO5zOCIEC/3zcG+/0e1+sVVVXhP8M5Ho+Ikgyz2Qyn0wmbzcYoo8PpdIrRaGTU0+n9fm+43W8oy/Lv2e0G5/F4oCpLFEVhIjAAFbbbbZP2fD7HcrnEarXCer1GkiRmT/Wc8zw3s9Ya2+0WzuNJMh36vm9wXRdCCGNIh8Q65xnZ7XYGrg+HQ63QwsE0GI1NoAqmbZWwaXbmY5aIAizMsk65qhqnz3XhnvcWa/9sZ3Geu8QHjMxuE64tVGCV8e5yuZhmcbY44/HYFJ4wFRadXR4MBhgOh+j1es2e9fQ8r/kF/AEsyWQyafgF41DttP0j760AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Request response view in Charles&quot; title=&quot;Request response view in Charles&quot; src=&quot;/static/ecfac156311009587988bd51d8c15dc1/2e9ed/20220228_charles_request_response.png&quot; srcSet=&quot;/static/ecfac156311009587988bd51d8c15dc1/12d18/20220228_charles_request_response.png 375w,/static/ecfac156311009587988bd51d8c15dc1/44507/20220228_charles_request_response.png 750w,/static/ecfac156311009587988bd51d8c15dc1/2e9ed/20220228_charles_request_response.png 1500w,/static/ecfac156311009587988bd51d8c15dc1/e90d0/20220228_charles_request_response.png 2250w,/static/ecfac156311009587988bd51d8c15dc1/b5233/20220228_charles_request_response.png 2322w&quot; sizes=&quot;(max-width: 1500px) 100vw, 1500px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Request and response view in Charles.&lt;/div&gt;&lt;p&gt;As we can see above, the top area shows the request with query parameters, form parameters, cookies, authentication and JSON headers. The bottom area shows the response. For both, we can switch between different views of the same data like text, raw, json etc.&lt;/p&gt;&lt;h3&gt;Chart view&lt;/h3&gt;&lt;p&gt;The chart view in Charles shows us request and response times of each request.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1116px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/6d2d47820a215f05fd2e2316f4d05c5b/94f3c/20220228_charles_chart.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:25.866666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAA1ElEQVQY042Ny26CABRE+f9/Khs2TQlUSkvBIsEiKOKLN1zE04BdGxcnmZzJZBTP89B1HdM0CYKAD8tisXhH0zRUVcUwDOI4Joqip1CyLJsHtm2TJDHnvMT1f9kmMVVdU5YleVFQPInSNC3p4Yzz7c4PvVypmp6+nxCGYeCU1xxOF0SG2YkITSf0Incn9zx1inQ12fGIv3RJNmvgCjeB8QE3oe06kl3KKC2Mw78fUJabHOtnz8urw9tnRLjvWG1rVruG4AFT/xXm+ElFmLZYQYGzvvAH5kB3QTK9Dd0AAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Chart view in Charles&quot; title=&quot;Chart view in Charles&quot; src=&quot;/static/6d2d47820a215f05fd2e2316f4d05c5b/94f3c/20220228_charles_chart.png&quot; srcSet=&quot;/static/6d2d47820a215f05fd2e2316f4d05c5b/12d18/20220228_charles_chart.png 375w,/static/6d2d47820a215f05fd2e2316f4d05c5b/44507/20220228_charles_chart.png 750w,/static/6d2d47820a215f05fd2e2316f4d05c5b/94f3c/20220228_charles_chart.png 1116w&quot; sizes=&quot;(max-width: 1116px) 100vw, 1116px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Chart view in Charles.&lt;/div&gt;&lt;h3&gt;Simulating slow networking&lt;/h3&gt;&lt;p&gt;Charles can be used to adjust the bandwidth and latency of the network connection. This allows us to test our application under poor networking conditions. Go to &lt;code&gt;Proxy -&amp;gt; Throttle Settings&lt;/code&gt; to see the available options.&lt;/p&gt;&lt;span class=&quot;gatsby-resp-image-wrapper&quot; style=&quot;position:relative;display:block;margin-left:0px;margin-right:auto;max-width:1080px&quot;&gt;
      &lt;a class=&quot;gatsby-resp-image-link&quot; href=&quot;/static/16c1ab084a91c15fb2feeadc6dc57613/dabea/20220228_charles_throttle.png&quot; style=&quot;display:block&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;
    &lt;span class=&quot;gatsby-resp-image-background-image&quot; style=&quot;padding-bottom:112.26666666666667%;position:relative;bottom:0;left:0;background-image:url(&amp;#x27;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAABYlAAAWJQFJUiTwAAACvUlEQVQ4y6WV0W7TMBSG86I8AELiAvEIgMRTAFdIu4EBV0NiIHEFG1tbZY2aNlnbNE0a20maNkkL0o9+F3dp6dAkLn7Zcnw+/+ccu7XkOEQQTRCLCJlQSIVEKm8UTUKE40ArGI2gEoFMqp09TVn2ZI6TixB2uw2n5+Ky1cbA8yGkQpwIPW6lUswScasSIWBJKXHhp/D6DqLAg9ezMRw4GA66yOUUq4VCVcit6n8oUxKWHyQ47SR4/9XFm88OTlsxvrRn+HQZ4+Q8xscfMU7uovMI/ngG66wn8OhojidvMzz/kOHZcYanxxkev1a4/1LiwSt1J3HvNyeBlWcpgDWAn/8tsiylFMqqPqyyuhlLs3b7XrIsIQT2lSSJHrMsw3A4RFEUmE6n6Pf7ep2N/Cvmz3gQyIA0TTWQc55M8RvXzaFNZTJBqg4ACQiCQLsZDAbaYRRFCMNQazwea8dVVaGuayyWJX6tljgbLmGP1OYeGhFIJ7PZTAeORiMNJ7jT6aDX6+k17mUJrq+vIZVCkWfoT1IMp3IDbLqbTCY60PM8+L6vHXLO4Pl8rl2VZakPcV0Xy+USRbFAXS6QZw2Hxh1FAB05jqPhPISBq9VKwzjSeZ7nWK/X+pCqrnV9tw45Mh06IdC2bV1HuuQ6G6QDq0qLjeDhhBiRsQOkE6bI+hFIh0yLUH436VKm6/vaSZkjnbDgBHa73W0jmB5TNQ6bMU3tODR3j11utVoaaBrCb3Ro6mgc7huyzCSOY52aER1eXV3plNlRdpgg1o7gJkSIjQ4C6chcFdaQMDrm5WZnWQJCzYsxMC0DbKZMsV6E8GUQxJfBOYGsL5WmSr9fPrc83UhKAYs1Y3dZeAYyiA4JNXezeUU4cp/bH6CQU7z4XuLeUY2H7yqEcSPl/eLuv6D99870+JPfDxS+uSlaPv9vEvwGRbteibqHI7IAAAAASUVORK5CYII=&amp;#x27;);background-size:cover;display:block&quot;&gt;&lt;/span&gt;
  &lt;img class=&quot;gatsby-resp-image-image&quot; alt=&quot;Network throttle with Charles&quot; title=&quot;Network throttle with Charles&quot; src=&quot;/static/16c1ab084a91c15fb2feeadc6dc57613/dabea/20220228_charles_throttle.png&quot; srcSet=&quot;/static/16c1ab084a91c15fb2feeadc6dc57613/12d18/20220228_charles_throttle.png 375w,/static/16c1ab084a91c15fb2feeadc6dc57613/44507/20220228_charles_throttle.png 750w,/static/16c1ab084a91c15fb2feeadc6dc57613/dabea/20220228_charles_throttle.png 1080w&quot; sizes=&quot;(max-width: 1080px) 100vw, 1080px&quot; style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0&quot; loading=&quot;lazy&quot;/&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;div class=&quot;imageCapture&quot;&gt;Network throttling with Charles.&lt;/div&gt;&lt;p&gt;We can apply throttling to all requests or only to specific hosts, so the rest of our networking requests aren&amp;#x27;t slowed down.&lt;/p&gt;&lt;h3&gt;Manipulating requests and responses&lt;/h3&gt;&lt;p&gt;Being able to manipulate response data is a great way to test how the app handles unexpected values.&lt;/p&gt;&lt;p&gt;Charles breakpoints tool lets us intercept and manipulate requests and responses before they are passed through Charles.&lt;/p&gt;&lt;p&gt;Check out the guide on &lt;a href=&quot;/20220307-manipulating-network-requests-and-responses-with-charles&quot;&gt;Manipulating requests and responses with Charles&lt;/a&gt; for more details on this topic.&lt;/p&gt;&lt;h3&gt;Further reading&lt;/h3&gt;&lt;p&gt;Charles offers even more advanced tools like repeating, automatic rewrites, map local, map remote, cache and cookies control and more. Check out their &lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;https://www.charlesproxy.com/documentation/tools/&quot;&gt;official tools documentation&lt;/a&gt; to check those out.&lt;/p&gt;</content:encoded><category>networking</category><category>tools</category><category>ios</category></item><item><title><![CDATA[Symmetric-key cryptography with CryptoKit for iOS]]></title><description><![CDATA[Using CryptoKit to increase security in iOS applications.]]></description><link>https://tanaschita.com/cryptokit-symmetric-key-cryptography</link><guid isPermaLink="false">https://tanaschita.com/cryptokit-symmetric-key-cryptography</guid><pubDate>Sun, 16 Jan 2022 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Unlike &lt;a href=&quot;/cryptokit-public-key-cryptography/&quot;&gt;public-key encryption&lt;/a&gt;, which uses separate keys for encryption and decryption, symmetric encryption relies on a single shared key. While this requires securely exchanging the key beforehand, it is significantly less complex and more performant than asymmetric encryption.&lt;/p&gt;&lt;p&gt;In real-world applications, both encryption methods are often used together:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Asymmetric encryption&lt;/strong&gt; is used to securely exchange a symmetric key.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Symmetric encryption&lt;/strong&gt; is used to encrypt and decrypt actual data efficiently.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#x27;s see how we can implement symmetric-key cryptography in iOS using &lt;code&gt;CryptoKit&lt;/code&gt;.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;h3&gt;Generating and exchanging a shared secret&lt;/h3&gt;&lt;p&gt;Before we can encrypt data with symmetric encryption, both parties (client and server) must share the same secret key. This is commonly achieved through public-key cryptography:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;The server generates a symmetric key, encrypts it using the client&amp;#x27;s public key, and sends it over.&lt;/li&gt;&lt;li&gt;The client decrypts the symmetric key using its private key.&lt;/li&gt;&lt;li&gt;Now, both the client and server have the same shared symmetric key, which they can use for fast encryption and decryption.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Once the symmetric key is obtained, we can create a &lt;code&gt;SymmetricKey&lt;/code&gt; instance in &lt;code&gt;CryptoKit&lt;/code&gt; as follows:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let keyData = Data(base64Encoded: base64EncodedKeyString)
let symmetricKey = SymmetricKey(data: keyData)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Encrypting and decrypting data&lt;/h3&gt;&lt;p&gt;Modern symmetric encryption algorithms use an approach called AEAD (Authenticated Encryption with Associated Data). The two most commonly used ciphers in this category are AES-GCM and ChaCha20-Poly1305.&lt;/p&gt;&lt;p&gt;Using CryptoKit, encrypting data with these algorithms is straightforward:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let encryptedData = try ChaChaPoly.seal(data, using: symmetricKey).combined
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The same can be achieved using AES-GCM:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let encryptedData = try AES.GCM.seal(data, using: symmetricKey).combined
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To decrypt the data, we reconstruct the sealed box and use the symmetric key:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let sealedBox = try ChaChaPoly.SealedBox(combined: encryptedData)
let decryptedData = try ChaChaPoly.open(sealedBox, using: symmetricKey)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Signing with symmetric key&lt;/h3&gt;&lt;p&gt;In addition to encryption, symmetric cryptography can also be used for verifying data integrity and authenticity through HMAC (Hash-based Message Authentication Code).&lt;/p&gt;&lt;p&gt;HMAC allows us to sign a message using a symmetric key, generating an authentication code that can be verified by the recipient. This is useful when ensuring that data has not been tampered with during transmission.&lt;/p&gt;&lt;p&gt;For that, we use the previously shared secret key to sign the digest of the data and then send both, the data and the signed digest to the server. The server uses the same key to validate the data.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let authenticationCode = HMAC&amp;lt;SHA512&amp;gt;.authenticationCode(for: data, using: symmetricKey)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This authentication code is sent along with the data. The recipient can then validate its authenticity:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;let isValid = HMAC&amp;lt;SHA512&amp;gt;.isValidAuthenticationCode(Data(authenticationCode),
                                                          authenticating: data,
                                                          using: symmetricKey)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;&lt;p&gt;CryptoKit provides a simple and powerful API for applying symmetric cryptography in iOS applications, whether for encrypting data, verifying integrity with HMAC, or securely exchanging keys.&lt;/p&gt;&lt;p&gt;By combining symmetric and asymmetric cryptography, we can create secure communication channels, protect sensitive data, and ensure data authenticity - all with just a few lines of Swift code.&lt;/p&gt;</content:encoded><category>cryptokit</category><category>security</category><category>swift</category><category>ios</category></item><item><title><![CDATA[How to preview SwiftUI views in dark mode]]></title><description><![CDATA[Preview SwiftUI views in dark mode by setting the preferredColorScheme(_:).]]></description><link>https://tanaschita.com/swiftui-dark-mode-preview</link><guid isPermaLink="false">https://tanaschita.com/swiftui-dark-mode-preview</guid><pubDate>Sun, 07 Nov 2021 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With dark mode becoming a common user preference on iOS devices, it&amp;#x27;s essential to test how your app&amp;#x27;s views look in both light and dark modes.&lt;/p&gt;&lt;affiliateView type=&quot;sponsor&quot;&gt;&lt;/affiliateView&gt;&lt;p&gt;Thankfully, SwiftUI makes this process effortless. We can use the &lt;code&gt;preferredColorScheme(_:)&lt;/code&gt; modifier in our previews and pass &lt;code&gt;.dark&lt;/code&gt; or &lt;code&gt;.light&lt;/code&gt; as the argument. Here&amp;#x27;s how to implement it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
          .preferredColorScheme(.dark)

        ContentView()
          .preferredColorScheme(.light)
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, we’re previewing the &lt;code&gt;ContentView&lt;/code&gt; in both dark and light mode.&lt;/p&gt;&lt;h3&gt;Quick tip: Toggle dark and light modes in the simulator&lt;/h3&gt;&lt;p&gt;When running your app in the iOS simulator, you can quickly switch between dark and light modes using the keyboard shortcut &lt;code&gt;CMD + SHIFT + A&lt;/code&gt;. This is a fast and convenient way to test UI elements dynamically while developing.&lt;/p&gt;</content:encoded><category>swiftui</category><category>ios</category></item></channel></rss>