<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Chris Eidhof</title>
		<description>
			Personal Blog
		</description>
		<link>http://chris.eidhof.nl</link>
		<atom:link href="http://chris.eidhof.nl/feed.xml" rel="self" type="application/rss+xml"/>
		<item>
			<title>Alt Tech Talks</title>
			<description>
				&lt;p&gt;
	In 2013, I got rejected for Apple’s “Tech Talks”. At the time, with objc.io doing well, I assumed I would have been accepted.
&lt;/p&gt;
&lt;p&gt;
	I jumped on my bike, cycled to the nearest cinema, and walked in asking what it would cost to rent the place for a day.
&lt;/p&gt;
&lt;p&gt;
	They said €3,000.
&lt;/p&gt;
&lt;p&gt;
	I realised I could probably get sponsors, and even if not, I could afford to lose it. So I said yes.
&lt;/p&gt;
&lt;p&gt;
	I called Microsoft and asked them to sponsor my alternative conference. I started asking friends to speak. I couldn’t pay them, but tickets would be free. The line-up filled up quickly.
&lt;/p&gt;
&lt;p&gt;
	On the day itself, I got out of the subway. It was still dark, and I saw “Alt Tech Talks” on the cinema lightbox. That was the first moment it felt real—and slightly terrifying. People were flying in from all over Europe.
&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://chris.eidhof.nl/images/alt-tech-talks.jpg&quot; title/&gt;&lt;/p&gt;
&lt;p&gt;
	The room filled up. The talks were great, but more importantly, the atmosphere was different. People meeting, talking, forming connections and making friends. It felt like a real coming together of the community.
&lt;/p&gt;
&lt;p&gt;
	Looking back, I didn’t actually need a ticket for Apple’s event. I needed a way to meet the community.
	And it turns out you can just build one.
&lt;/p&gt;
&lt;p&gt;
	What I didn’t expect was that others would pick it up. People started organizing their own versions of Alt Tech Talks. &lt;a href=&quot;https://github.com/Daniel1of1&quot;&gt;Daniel&lt;/a&gt; ran one in London. At that point, it stopped being about my rejection and became something bigger than that.
&lt;/p&gt;
			</description>
			<pubDate>Fri, 03 Apr 2026 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/alt-tech-talks</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/alt-tech-talks</guid>
		</item>
		<item>
			<title>Building Pancake</title>
			<description>
				&lt;p&gt;
	In January, Nathan and I built a Mac app over the course of a month. Last year, I set the goal to collaborate more with other people in 2026, and Nathan was the first person I thought of. We’ve been doing SwiftUI workshops together throughout last year, and I really enjoy working with him. This post is a short story of what we’ve built and how we did it.
&lt;/p&gt;
&lt;p&gt;
	Our goal was to build and ship an app in a month. This is slightly ambitious, and we both succeeded and failed at the same time. We decided last minute that we’d build a Mac app to build static sites. Many static site generators come with all kinds of dependencies that eventually fail to resolve, and installation can be messy. With Mac apps, everything comes bundled and you get the experience of “batteries included”.
&lt;/p&gt;
&lt;p&gt;
	We decided that we wanted to scope it down to just creating blogs (initially) and speed was a core consideration: it should be extremely fast to create a new website (creating a new file), to write something (integrated editor) and also to publish it (our own server). Because we use a native text editor, adding images is as easy as just dragging the image in. In the video below, you can see exactly this process with Pancake, our new app:
&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/A22kuc9k0Os?si=YYXs1_abT-Ci4pRy&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;/iframe&gt;

&lt;p&gt;
	We decided beforehand that we wanted to release this at the end of the month. In our case, this meant sharing the download link with just a few close friends. While we both have other obligations this month and next, we haven’t found the time to polish this in a way that we feel comfortable putting this out as a “public” release. But if you are interested, just send one of us a message.
&lt;/p&gt;
&lt;p&gt;
	I’m proud of what we built in a month. I do think we made a few mistakes. First of all, I think we had too big of a scope given the constraints. Second, including a server component means a lot of responsibility (what if people upload malicious content? what if the server goes down?) that feels unrealistic to support as a side project.
&lt;/p&gt;
&lt;p&gt;
	We’re not sure yet how we’ll proceed from here. Is this a portfolio piece? Will we keep working on it? In any case, it has been a lot of fun. For me, the highlight was actually working together with Nathan, in person, in Paris.
&lt;/p&gt;
			</description>
			<pubDate>Fri, 20 Feb 2026 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/building-pancake</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/building-pancake</guid>
		</item>
		<item>
			<title>Self-Forking Agents</title>
			<description>
				&lt;p&gt;
	Over the last weeks, I’ve been experimenting with building agents. An agent in itself is a simple concept. It’s a loop where you give it input, and then the LLM responds. Crucially, you can tell the LLM to use specific tools that you describe, and the LLM’s response can include calls to those tools.
&lt;/p&gt;
&lt;p&gt;
	There is an inner loop where, before accepting the next user input, you process all the tool calls and feed that information back into the LLM. And it turns out you can write an agent with very few tools. The &lt;a href=&quot;https://github.com/badlogic/pi-mono&quot;&gt;PI agent&lt;/a&gt; uses just four: read, write, edit, and bash.
&lt;/p&gt;
&lt;p&gt;
	I built a few things on top of Pi. I made a Mac GUI using SwiftUI, which turned out to be relatively useless for now. Then I wanted to take a key concept from &lt;a href=&quot;https://github.com/openclaw/openclaw&quot;&gt;OpenClaw&lt;/a&gt;, which is &lt;em&gt;self-improving code&lt;/em&gt;
	. Here’s the goal I had in mind: I wanted to talk to an agent (running on a server) through Telegram and tell it to improve its own code. To do so, I wanted it to fork itself using a git worktree, make the changes, verify them and then open a pull request. This way I can talk to my agent and have it improve itself over time.
&lt;/p&gt;
&lt;p&gt;
	It took me a few hours of prompting to get this up and running. First, I created a &lt;a href=&quot;https://github.com/chriseidhof/pi-agent/blob/main/.pi/skills/self-fork/SKILL.md&quot;&gt;&lt;em&gt;self-fork&lt;/em&gt;
		 skill&lt;/a&gt;. A skill is just a text file that describes how to do something specific, and it took a few tries to get this right. One of the key insights was to have as little code in the text as possible. I moved almost all the code out into separate scripts. This makes the skill much more reliable. Given what I learned, I do think I can prompt my way to the same result much quicker now.
&lt;/p&gt;
&lt;p&gt;
	For me, this is the way I like to learn. I recreate things from the ground up so that I understand how it works. And I feel that I’ve now understood a key part of how to build something like OpenClaw. Of course, there is a lot more to it, but to me, this was one of the pieces I wanted to see for myself.
&lt;/p&gt;
&lt;p&gt;
	If you want to try this yourself, here’s the goal to aim for: build an agent that you can talk to through a chat app. You should be able to tell the agent to improve itself and get that improvement out as a GitHub pull request. The agent needs to run on a server so that you can always access it. Getting this loop closed felt really magical to me. Good luck!
&lt;/p&gt;
			</description>
			<pubDate>Fri, 13 Feb 2026 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/self-forking-agents</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/self-forking-agents</guid>
		</item>
		<item>
			<title>Integrating Dependencies into LLM-Assisted Projects</title>
			<description>
				&lt;p&gt;
	The other week I met &lt;a href=&quot;https://orta.io&quot;&gt;Orta&lt;/a&gt; again. When talking about LLM-assisted coding, we also discussed dependencies. He told me about an approach he’s been taking: he just integrates dependencies into his code by literally pasting in the files and then adjusting them as needed. He also &lt;a href=&quot;https://github.com/puzzmo-com/tapped?tab=readme-ov-file#what-is-tapped&quot;&gt;recommends this&lt;/a&gt; way of integrating for one of his projects. To me, it almost feels like “melting” the dependency into the codebase. You start owning the code the moment you integrate.
&lt;/p&gt;
&lt;p&gt;
	This is very different from having a dependency manager where you depend on the code other people maintain. Over the years, even before LLMs, I’ve done the same with my own code. Rather than abstracting things out, I often would just paste in existing code and modify it for that project. With LLMs, it’s now become even easier to do that.
&lt;/p&gt;
&lt;p&gt;
	Even Donald Knuth was skeptical about reusable code:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		I also must confess to a strong bias against the fashion for reusable code. To me, “re‐editable code” is much, much better than an untouchable black box or toolkit. […] (&lt;a href=&quot;https://mmix.cs.hm.edu/other/knuth-interview.pdf&quot;&gt;Source&lt;/a&gt;)
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	In any case, I’ve been moving towards &lt;a href=&quot;https://chris.eidhof.nl/post/fewer-dependencies/&quot;&gt;fewer dependencies&lt;/a&gt; for years, maybe now even more? I’m curious about other changes that might happen as well.
&lt;/p&gt;
			</description>
			<pubDate>Thu, 05 Feb 2026 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/integrating-dependencies-into-llm-assistant-projects</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/integrating-dependencies-into-llm-assistant-projects</guid>
		</item>
		<item>
			<title>Food Assistant</title>
			<description>
				&lt;p&gt;
	This month, I want to focus on using LLMs more, and AI in general, so I’m working on a bunch of projects. I’ve been using Claude (and more recently Codex) over the last year or so, but now I’m actually using them very intentionally with the goal of understanding how to work with them better and to understand what the possibilities are.
&lt;/p&gt;
&lt;p&gt;
	This morning I built a small custom agent. I used the new Codex Mac app to build this agent from scratch, and of course I’ve been inspired by the work that &lt;a href=&quot;https://steipete.me&quot;&gt;Peter&lt;/a&gt; has been doing with &lt;a href=&quot;https://openclaw.ai&quot;&gt;OpenClaw&lt;/a&gt;. This agent is taking care of my food, so it has an idea of what I have in stock. It knows about my allergies, my dietary wishes, and it has recipes and a grocery list. It also has a list of dishes I enjoy making and eating. I managed to get it up and running in about half a day.
&lt;/p&gt;
&lt;p&gt;
	I started by creating a new TypeScript project and using the &lt;a href=&quot;https://openai.github.io/openai-agents-js/&quot;&gt;Agents SDK&lt;/a&gt; from OpenAI. The main interface is a Telegram bot because Telegram has strong support for bots, and that way I have both a text and a voice interface for free. If I send it a voice message, it uses ffmpeg to convert the audio, then I send it over to OpenAI to get it transcribed.
&lt;/p&gt;
&lt;p&gt;
	The agent operates on Markdown files; they all live in a Git repository, and every time I change something it gets committed automatically. Also, the agent pulls from Git automatically. I’ve used a &lt;a href=&quot;https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys&quot;&gt;GitHub deploy key&lt;/a&gt; to give the agent access to just the one repository. This gives me persistent memory, change tracking, and I can just host my agent on Fly.io. I’m really liking this pattern. I wonder if I could extract it out into something more reusable. I
&lt;/p&gt;
&lt;p&gt;
	One of the fun things is that the system prompt is in a Markdown file as well, so I can actually tell my agent to change its prompt and that will automatically get reloaded. This is a powerful concept. Of course, things like OpenClaw can completely rebuild and rewrite themselves, which is even more powerful. I did not want that for this project, but being able to talk and chat to my software in order to then rewrite itself is very powerful.
&lt;/p&gt;
&lt;p&gt;
	One interesting thing is that the agent initially wanted to add tools for “writing the shopping list”, “writing the recipe list”, etc. When I changed that to just read/write markdown file tools the results got dramatically better.
&lt;/p&gt;
&lt;p&gt;
	I finally added some support for images and meal planning as well, so I can be a little bit smarter about going shopping and reusing things and meal prepping certain ingredients. All in all, it was half a day of work, and it’s already very useful. It’s online all the time, I can talk to it, it has no access beyond the files it needs, and we’ll see how I will use this in the future.
&lt;/p&gt;
&lt;p&gt;
	Today, I used it to plan my next four meals and my shopping list. It updated my plan, gave me a few recipe ideas and made me a shopping list.
&lt;/p&gt;
			</description>
			<pubDate>Tue, 03 Feb 2026 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/llm-month-food-assistant</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/llm-month-food-assistant</guid>
		</item>
		<item>
			<title>2025</title>
			<description>
				&lt;p&gt;
	This has been a very eventful year for me personally. While I don’t want to talk publicly about all the bad things that happened, there’s been enough for a year.
&lt;/p&gt;
&lt;p&gt;
	Yet, this is a hopeful post. I’m doing well given the circumstances. I learned a whole bunch of new things about myself. I found much more compassion for both myself and for others. I was able to show up in ways I couldn’t imagine before. There are new people in my life that already feel like they’ve been there for years.
&lt;/p&gt;
&lt;p&gt;
	Some of the things from 2025 will continue to drag on in the new year. There’s going to be a lot of adjustment, but there will also be many new opportunities.
&lt;/p&gt;
&lt;p&gt;
	I decided to take action on some things that bothered me and will collaborate much more in 2026. I’ll start in January by doing a project with &lt;a href=&quot;https://cykele.ro&quot;&gt;Nathan&lt;/a&gt; and hope to collaborate much more with others during the rest of the year. I’m planning to be in Berlin more regularly as well, and hope to be more active in the social part of the tech scene.
&lt;/p&gt;
&lt;p&gt;
	This year, I’m most proud of how I was able to reframe some really bad situations. Instead of trying to control things that were out of my control, I was able to show up in the best way possible. I showed up full of compassion and understanding. For the first time in my life, it was really clear to me that I chose to act well even when other people did not. I optimized against regret. It’s not the first time in my life where a stressful situation actually made me get the best out of me.
&lt;/p&gt;
&lt;p&gt;
	My new year’s resolution for 2026 is to celebrate more. Specifically, I want to celebrate the achievements of the people around me as well as my own achievements. I want to celebrate friendships, collaborations and other good things. If you have something to celebrate, reach out and we’ll find a good way!
&lt;/p&gt;
			</description>
			<pubDate>Wed, 31 Dec 2025 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2025</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2025</guid>
		</item>
		<item>
			<title>Task Identity</title>
			<description>
				&lt;p&gt;
	When you write SwiftUI views, one of the big advantages over UIKit is that SwiftUI performs automatic dependency tracking. Whenever your model invalidates or one of your view’s properties change, your view is re-rendered.
&lt;/p&gt;
&lt;p&gt;
	Consider the following view that loads an image. It runs the image loading code in a &lt;code&gt;task&lt;/code&gt;. The task will run the first time the view appears, and it seems to work:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ImageLoader: View {
    var url: URL
    @State private var loaded: NSImage? = nil
    var body: some View {
        ZStack {
            if let loaded {
                Image(nsImage: loaded)
            } else {
                Text(&amp;quot;Loading…&amp;quot;)
            }
        }
        .task {
            guard let data = try? await URLSession.shared.data(from: url).0 else { return }
            loaded = NSImage(data: data)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	When we use the view in our &lt;code&gt;ContentView&lt;/code&gt;, everything seems fine:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    var body: some View {
        ImageLoader(url: URL(string: &amp;quot;https://picsum.photos/200/300&amp;quot;)!)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	However, there is a subtle bug in the initial code that is really hard to spot. The problem is that &lt;code&gt;task&lt;/code&gt; runs the code exactly once — when the view appears. When the &lt;code&gt;url&lt;/code&gt; property changes, the view’s body will be re-rendered, but the task will not be re-run as the view has already appeared. If you use &lt;code&gt;onAppear&lt;/code&gt; instead of &lt;code&gt;task&lt;/code&gt; you’ll have exactly the same problem.
&lt;/p&gt;
&lt;p&gt;
	We can verify by changing our &lt;code&gt;ContentView&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    @State private var height = 300
    var body: some View {
        ImageLoader(url: URL(string: &amp;quot;https://picsum.photos/200/\(height)&amp;quot;)!)
            .onTapGesture {
                height = height == 300 ? 200 : 300
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	When we run the code above, we can see that the image never loads again (beyond the initial load) even when the URL changes. We can put a print statement or breakpoint in the &lt;code&gt;ImageLoader&lt;/code&gt;’s body and verify that the body is actually rerun when the URL changes.
&lt;/p&gt;
&lt;p&gt;
	The problem is our &lt;code&gt;task&lt;/code&gt;: it runs when the view appears. And only when the view appears.
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;
		 Unfortunately, &lt;code&gt;task&lt;/code&gt; and &lt;code&gt;onAppear&lt;/code&gt; are currently the best way to run code when a view gets created in the &lt;a href=&quot;https://chris.eidhof.nl/note/attribute-graph/&quot;&gt;attribute graph&lt;/a&gt;. However, the concept of “view appearance” is not clearly defined in SwiftUI. For example, the task will re-run when you put the image loader in a &lt;code&gt;List&lt;/code&gt; and scroll it out of bounds and back into bounds. Likewise, when you put the image loader in a tab view, the task will also run again when switching tabs.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	To fix our problem, we need to understand the core issue. Let’s look at the code again:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ImageLoader: View {
    var url: URL
    @State private var loaded: NSImage? = nil
    var body: some View {
        // ...
        .task {
            guard let data = try? await URLSession.shared.data(from: url).0 else { return }
            loaded = NSImage(data: data)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Inside our &lt;code&gt;task&lt;/code&gt; &lt;em&gt;we have a dependency on the &lt;code&gt;url&lt;/code&gt; property&lt;/em&gt;
	. This is causing our problems: ideally, we want our &lt;code&gt;task&lt;/code&gt; to re-run whenever the &lt;code&gt;url&lt;/code&gt; changes. We can do so by making the &lt;code&gt;url&lt;/code&gt; part of the identity of our &lt;code&gt;task&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;.task(id: url) {
    guard let data = try? await URLSession.shared.data(from: url).0 else { return }
    loaded = NSImage(data: data)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	This now makes our code work correctly. The task will run initially, and when the identity changes, the previous task gets cancelled and a new task is created.
&lt;/p&gt;
&lt;h3&gt;
	Adding More Properties
&lt;/h3&gt;
&lt;p&gt;
	Let’s consider a slightly more complicated example. Here we have a &lt;code&gt;task&lt;/code&gt; that depends on two properties, &lt;code&gt;baseURL&lt;/code&gt; (from the environment) and &lt;code&gt;path&lt;/code&gt; (a regular property):
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ImageLoader: View {
    @Environment(\.baseURL) private var baseURL
    var path: String

    @State private var loaded: NSImage? = nil

    var body: some View {
        // ...
        .task {
            let url = baseURL.appendingPathComponent(path)
            guard let data = try? await URLSession.shared.data(from: url).0 else { return }
            loaded = NSImage(data: data)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	To make this work correctly, we now need to create a &lt;em&gt;compound&lt;/em&gt;
	 identity that combines both the &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;baseURL&lt;/code&gt;, as the &lt;code&gt;task&lt;/code&gt; depends on both. In addition, this value needs to conform to &lt;code&gt;Equatable&lt;/code&gt;. An easy way to solve this is by taking all your dependencies and sticking them into an &lt;code&gt;[AnyHashable]&lt;/code&gt;, for example by saying &lt;code&gt;[baseURL as AnyHashable, path as AnyHashable]&lt;/code&gt;. In this specific example, we could actually pull out the &lt;code&gt;url&lt;/code&gt; property — as it already combines both values — and use that as the identity:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ImageLoader: View {
    // ...

    var body: some View {
        let url = baseURL.appendingPathComponent(path)
        // ...
        .task(id: url) {
            guard let data = try? await URLSession.shared.data(from: url).0 else { return }
            loaded = NSImage(data: data)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Here’s my advice: whenever you use &lt;code&gt;task&lt;/code&gt; or &lt;code&gt;onAppear&lt;/code&gt; in your code, be extremely careful about the dependencies. Make sure to review your code carefully, and ideally, have someone else review it as well.
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;
		 When you use &lt;code&gt;onAppear&lt;/code&gt; instead of &lt;code&gt;task&lt;/code&gt;, you can replace the &lt;code&gt;onAppear&lt;/code&gt; with an &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/onchange(of:initial:_:)-4psgg&quot;&gt;&lt;code&gt;onChange(of:initial:_:)&lt;/code&gt;&lt;/a&gt;. As the &lt;code&gt;value&lt;/code&gt;, you’d use the compound identity.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	Clearly, Apple can’t just change the implementation of &lt;code&gt;task&lt;/code&gt; or &lt;code&gt;onAppear&lt;/code&gt; to make this work automatically, as I’m sure there are many apps that depend on the current behavior. I wonder if it can even be done automatically without introducing cycles in the graph.
&lt;/p&gt;
&lt;h3&gt;
	Update
&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://m.objc.io/@ricketson@hachyderm.io/115543342730322506&quot;&gt;Matt Ricketson&lt;/a&gt; (SwiftUI Engineering Manager) wrote a very thoughtful response on Mastodon. I’ll quote it here so that it’s persistent in the future as well:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		It was intentional to not have automatic dependency tracking with this API, as originally designed — that can very easily become an unintended (and equally unintuitive) footgun in other ways when applied to imperative code with side effects (which View.body is not):
	&lt;/p&gt;
	&lt;ol&gt;
		&lt;li&gt;
			&lt;p&gt;
				First, as you mention at the end, automatic dependency cycle detection is more problematic, and if cycles do occur they can be much more difficult for a developer to detect and debug on their own.
			&lt;/p&gt;
		&lt;/li&gt;
		&lt;li&gt;
			&lt;p&gt;
				There are common kinds of view tasks that are intended to only be run once, relative to the view’s lifetime, such as continuous tasks that manually track other values (e.g. a &lt;code&gt;for await&lt;/code&gt; on an AsyncSequence of some kind, and note that task() predates the existence of Observable).
			&lt;/p&gt;
		&lt;/li&gt;
		&lt;li&gt;
			&lt;p&gt;
				There are many cases where a view task will read/write cached values that should not participate in dependency tracking, so you would still need a similar API but inverted, to be able to specify the values that should be ignored (similar to purpose of @ObservationIgnored).
			&lt;/p&gt;
		&lt;/li&gt;
	&lt;/ol&gt;
	&lt;p&gt;
		Of course, none of that contradicts your blog post! The image loading example is a great example of a common type of “pure function” task where it &lt;em&gt;would&lt;/em&gt;
		 be convenient and intuitive for automatic dependency tracking to occur, and the point about compound dependencies is a legitimate ergonomic problem!
	&lt;/p&gt;
&lt;/blockquote&gt;
			</description>
			<pubDate>Thu, 13 Nov 2025 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/swiftui-task-identity</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/swiftui-task-identity</guid>
		</item>
		<item>
			<title>LLMs for &quot;Real Projects&quot;</title>
			<description>
				&lt;p&gt;
	When I have an LLM assistant write prototypes in a language that I don’t know, my prompts are high-level and focused on features, not implementation. For simple projects, often a one-shot approach works. For example, I’ll say:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		take these notes from my therapist and help me build a simple react app that asks me those questions.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	For the project I was building above, I didn’t really care about the code, I just wanted it to work.
&lt;/p&gt;
&lt;p&gt;
	When I write SwiftUI, I’d like to have a &lt;em&gt;lot&lt;/em&gt;
	 more control, because that’s the domain I know. I care about the code. My prompts become a lot more technical and implementation-specific. For example, I said:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		When hovering over an anchor/socket, propagate that anchor’s bounds, including its unit point up using a preference. The preference should contain an array of all the anchors that are currently hovered over.  I think we should do the bounds as an Anchor&amp;lt;CGRect&amp;gt;. Before we do this, we should extract the socket view out into its own view.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	My prompts are typically smaller, but still: this prompt worked really well. I was already in the context of making edits to that node view that contained the sockets. The code it generated was very close to what I would’ve written myself.
&lt;/p&gt;
&lt;p&gt;
	One interesting note: somewhat confusingly, I have anchors in my code that represent the sockets a node has (I’m building a small drawing app). Claude Code did not confuse them with SwiftUI’s builtin &lt;code&gt;Anchor&lt;/code&gt; values.
&lt;/p&gt;
			</description>
			<pubDate>Wed, 09 Jul 2025 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/llms-real-code</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/llms-real-code</guid>
		</item>
		<item>
			<title>Agentic Coding</title>
			<description>
				&lt;p&gt;
	I teach SwiftUI for a living. Through our &lt;a href=&quot;https://www.swiftuifieldguide.com/workshops/&quot;&gt;workshops&lt;/a&gt;, books, and videos we try to help people build a mental model of how SwiftUI works as well as show some of the details. I really, really enjoy doing that and have built a successful lifestyle business doing just that.
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		Note: this post was written by me, not by an LLM. I don’t feel comfortable at all letting it do that.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	To me, the quality of the LLMs and their tooling (looking at you, Claude Code) feel like a threat to my business. Will people still need to learn SwiftUI? Do we need to teach in different ways? What will the LLMs be capable of in a year from now? Will there still be enough people to teach?
&lt;/p&gt;
&lt;p&gt;
	I’m in an ongoing process of figuring this out. I’ve experimented a whole bunch with LLMs, have read and watched many things (&lt;a href=&quot;https://steipete.me/posts/2025/essential-reading&quot;&gt;Essential Reading for Agentic Engineers&lt;/a&gt; is a really good start). In this post, I’ll try to share some of the stuff I learned. I’m by no means an expert, but I still hope that this is useful.
&lt;/p&gt;
&lt;h2&gt;
	Easy Things You Can Do Today
&lt;/h2&gt;
&lt;p&gt;
	If you are not using an LLM or have not had success using it (“it just didn’t work for me”) here are some quick ideas in which it can help you today. I’m mainly using Claude Code (and sometimes the Claude desktop app):
&lt;/p&gt;
&lt;p&gt;
	Using the Claude desktop app, you can very quickly build basic prototypes. This can be extremely helpful in understanding a problem and can be a big motivation to build it “properly”. If you need a quick web service prototype, you can use tools like &lt;a href=&quot;https://www.val.town&quot;&gt;val.town&lt;/a&gt; to host (and further refine) stuff.
&lt;/p&gt;
&lt;p&gt;
	If you are working with an existing code base and don’t want an LLM touching your precious code (this is often how I feel!), you can have an LLM build little debugging tools. For example, I had it visualize some internal state for me during debugging. Seeing the current state in my app was much quicker to parse than reading through the debug output. Once I found the problem I immediately deleted the debug code.
&lt;/p&gt;
&lt;p&gt;
	I found it helpful to get a quick grasp of existing code. I just took a bunch of code and asked the LLM to explain it. I absolutely do not feel confident about my understanding after reading the explanation. It just helps to get a first basic feeling so that I can explore further.
&lt;/p&gt;
&lt;p&gt;
	I’ll also ask an LLM about possible algorithms I could use to solve problems. I’ll tell it I know Swift, Haskell and Typescript. I’ll make it generate examples. I don’t trust the output, and will verify things myself.
&lt;/p&gt;
&lt;p&gt;
	I found it helpful to ask an LLM to give me options to implement stuff (while also telling it to explicitily not implement it). I’ll use it to help me think through problems.
&lt;/p&gt;
&lt;p&gt;
	If I feel stuck on something or somehow can’t get started, I can always prompt the LLM to start on something tiny. I can then carry that momentum forward myself.
&lt;/p&gt;
&lt;p&gt;
	If I need to generate mock data (e.g. for my workshops) I’m often too lazy myself to really put time into it. Even the builtin LLM to Xcode has been giving me way better results than doing it myself.
&lt;/p&gt;
&lt;p&gt;
	Personally, if I think of my app’s architecture, I’m much more willing to let an LLM generate “leaf nodes” that can easily be replaced rather than trusting it with “core” nodes that require much more consideration.
&lt;/p&gt;
&lt;p&gt;
	I did find that getting better at prompting really can improve the results you’re getting.
&lt;/p&gt;
&lt;h2&gt;
	Weird Things
&lt;/h2&gt;
&lt;p&gt;
	Things are changing &lt;em&gt;so&lt;/em&gt;
	 quickly. It’s hard to keep up with new things, what might have been true last month is not true anymore this month. My setup is pretty simple: I mainly use Claude Code and the Claude desktop app.
&lt;/p&gt;
&lt;p&gt;
	I often “feel bad” about deleting code that was just written. The AI will often just write the wrong thing. I take full responsibility for the result (I either need to improve my instructions or write it myself). It’s okay to delete the just-generated code, ignore the responses and start over. However, I do still anthropomorphize the LLM: it’d be hard to tell a human to throw away what they just did, and I still feel a bit bad about it when doing that to the LLM.
&lt;/p&gt;
&lt;p&gt;
	I’ll have the LLM do non-code stuff as well. For example, you can tell Claude Code to create a git commit, to set up parts of your dev environment, and so on.
&lt;/p&gt;
&lt;h2&gt;
	What Doesn’t Work
&lt;/h2&gt;
&lt;p&gt;
	I don’t really believe in “vibe coding” beyond basic prototypes. To build quality non-trivial software, I think you (still?) need to have a very good understanding of what’s happening. For example: I asked Claude Code to add an inspector to an app I’m working on. Rather than using the &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10161&quot;&gt;inspector API&lt;/a&gt;, it generated an HStack. When I specifically told it to use the inspector API, it did do so. I still needed to change things myself. In other words: writing code with AI is absolutely not a fully automated process for me. I don’t shy away from writing code.
&lt;/p&gt;
&lt;p&gt;
	I had some fun with a side project and it looked like things were going well. I was doing TDD and iterating quickly. I got lazy, did not review the code, but trusted my tests. Turns out, the agent had been adding special cases to the code just to make the tests pass.
&lt;/p&gt;
&lt;p&gt;
	Another “fun” thing is that these models are not lossless. For example, I wanted to move some code to a different file. While doing this, Claude Code just hallucinated some new code and changed some of the old code. This becomes hard to spot when the diff gets large (when the diff gets too large, I’ll just undo the change and do stuff myself).
&lt;/p&gt;
&lt;p&gt;
	I had people in my workshop that used an LLM to generate a whole bunch of code that wasn’t working and not using the suggested APIs. Instead of using SwiftUI’s animation system, they animated with a timer. They were solving the wrong problem in the wrong way. The goal of the workshop was understanding, and not getting through the exercises as quickly as possible.
&lt;/p&gt;
&lt;h2&gt;
	You Still Need to Understand What’s Happening
&lt;/h2&gt;
&lt;p&gt;
	I feel like I still need a very strong understanding about the APIs that exist, about how to structure the code and about the mental model behind SwiftUI. My iterations are pretty short. I want to understand &lt;em&gt;exactly&lt;/em&gt;
	 what is happening.
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/steipete.me&quot;&gt;Peter&lt;/a&gt; seems to have a lot of success with a more hands-off approach, letting his agents run for a very long time.
&lt;/p&gt;
&lt;h2&gt;
	Feedback Loops
&lt;/h2&gt;
&lt;p&gt;
	The current thing I’m thinking about is how we can increase the feedback loop between the agent and me. This is the current loop:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;
			I prompt the agent to make a change.
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			The agent starts working, this often takes a while (30s to a few minutes). The better my prompt is, the quicker the work often goes.
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			The agent verifies its own work (by running the tests, compiling the project, etc.)
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			I need to verify what happened. Because I do a lot of GUI stuff, this often involves running and clicking through the app (or opening different previews in Xcode).
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			At this point, I’ll have three choices: I commit when I’m happy, I iterate to refine the solution, or I start over.
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	I think the second step (agent building things) will get much quicker over time. I think the third step (agent verifying its own work) still has a lot of untapped potential. But by far, the slowest part of the loop is me verifying what happened.
&lt;/p&gt;
&lt;h2&gt;
	Opportunities
&lt;/h2&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;
			It’s hard to keep up. A good reliable source of information could be helpful. (I’m considering turning what I’ve learned (and will learn) into a workshop).
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			We can build tools to help the agent work more effectively. Linters, type checkers, tests, but possibly also other methods of formal verification.
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			We can build tools to shorten the feedback loop (how can &lt;em&gt;we&lt;/em&gt;
			 verify the agents’ work more quickly?)
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			…
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
	Conclusion
&lt;/h2&gt;
&lt;p&gt;
	I don’t see a future without agentic coding. It feels like we’re just at the start. I think we still need a very strong understanding of what’s happening to be truly effective. It feels like things are changing very quickly, and none of the above might be true in a few days, weeks or months.
&lt;/p&gt;
			</description>
			<pubDate>Tue, 08 Jul 2025 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/agentic-coding</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/agentic-coding</guid>
		</item>
		<item>
			<title>Presentation: Attribute Graph</title>
			<description>
				&lt;p&gt;
	I gave a talk about my understanding of the Attribute Graph. I created a &lt;a href=&quot;http://chris.eidhof.nl/presentations/attribute-graph/&quot;&gt;presentation page&lt;/a&gt; that contains the video, an edited transcript and some references.
&lt;/p&gt;
&lt;p&gt;
	It was really fun creating the talk: I used my “record typing” tool to automate the typing and animate the graph, preview and other bits. The graphs are draw in SwiftUI, but the layout is done in GraphViz.
&lt;/p&gt;
			</description>
			<pubDate>Tue, 01 Jul 2025 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/attribute-graph-presentation</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/attribute-graph-presentation</guid>
		</item>
		<item>
			<title>SwiftUI View Model Ownership</title>
			<description>
				&lt;p&gt;
	When we cover SwiftUI’s state system in &lt;a href=&quot;https://www.swiftuifieldguide.com/workshops/&quot;&gt;our workshops&lt;/a&gt;, we often get asked: &lt;em&gt;How can I set up my view model in a view?&lt;/em&gt;
	 There’s a bit more to this question, so let’s try to spell out the requirements:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;
			We want our view to create a view model and maintain ownership: when the view goes away, the object should go away.
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			We want our view model to be an object (not a struct) and the object should use the &lt;a href=&quot;https://developer.apple.com/documentation/observation&quot;&gt;Observation&lt;/a&gt; framework.
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	Getting this right is trickier than expected.
&lt;/p&gt;
&lt;p&gt;
	Let’s consider a view model that counts the number of people in a room. Here’s the model definition:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;@Observable class RoomVM {
    let roomName: String
    var count: Int = 0
    init(roomName: String) {
        self.roomName = roomName
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	When we now want to create a &lt;code&gt;RoomView&lt;/code&gt; we are faced with a choice: we need to think about ownership. When the lifetime of our view model is tied to the lifetime of the &lt;code&gt;RoomView&lt;/code&gt;, we say that the &lt;code&gt;RoomView&lt;/code&gt; is the &lt;em&gt;owner&lt;/em&gt;
	 of our object. That means we should use an &lt;code&gt;@State&lt;/code&gt; property. When the view model is passed from the outside, we are not the owner, and we should not use an &lt;code&gt;@State&lt;/code&gt; property.
&lt;/p&gt;
&lt;p&gt;
	In our requirements, we wanted the &lt;code&gt;RoomView&lt;/code&gt; to be the owner. This means we should use an &lt;code&gt;@State&lt;/code&gt; property. My personal rule of thumb is to always mark all &lt;code&gt;@State&lt;/code&gt; properties as private and to always initialize them on the same line as the declaration. For example, for a simple &lt;code&gt;Int&lt;/code&gt; property I’d write:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct CounterView: View {
    @State private var value = 0
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	If you are unable to use those two rules (marking as private and setting the initial value), you should either reconsider using a &lt;code&gt;@State&lt;/code&gt; property or you should make sure to pay extra attention to the code you’re writing. In our case, we want an API that looks like &lt;code&gt;RoomView(name: &amp;quot;Main Room&amp;quot;)&lt;/code&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct RoomView0: View {
    var name: String
    @State var viewModel: RoomVM = // ...
    var body: some View {
        LabeledContent(viewModel.roomName) {
            Stepper(&amp;quot;\(viewModel.count)&amp;quot;, value: $viewModel.count)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;
	The init Trap
&lt;/h2&gt;
&lt;p&gt;
	What do we write after the equals sign above? Ideally, we’d write &lt;code&gt;RoomVM(roomName: name)&lt;/code&gt; but that doesn’t compile, because the &lt;code&gt;name&lt;/code&gt; is not available yet. Luckily after a bit of searching, we’ll find a solution somewhere on a blog, forum post or in a video:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct RoomView1: View {
    var name: String
    @State var viewModel: RoomVM
    init(name: String) {
        self.name = name
        self.viewModel = RoomVM(roomName: name)
    }

    var body: some View {
        LabeledContent(viewModel.roomName) {
            Stepper(&amp;quot;\(viewModel.count)&amp;quot;, value: $viewModel.count)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	The code above is broken, and it is not obvious. For example, if you run the following snippet, it works exactly as intended. We can navigate to a room, change the value, and when we navigate away the view model is destroyed.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    var rooms = [&amp;quot;Main Room&amp;quot;, &amp;quot;Breakout&amp;quot;, &amp;quot;Hallway&amp;quot;]
    var body: some View {
        NavigationView {
            List {
                ForEach(rooms, id: \.self) { room in
                    NavigationLink(room) {
                        RoomView1(name: room)
                    }
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Let’s now consider a different way of using our &lt;code&gt;RoomView1&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    var rooms = [&amp;quot;Main Room&amp;quot;, &amp;quot;Breakout&amp;quot;, &amp;quot;Hallway&amp;quot;]
    @State var selectedRoom: String = &amp;quot;Main Room&amp;quot;
    var body: some View {
        VStack {
            RoomView1(name: selectedRoom)
            Picker(&amp;quot;Select a room&amp;quot;, selection: $selectedRoom) {
                ForEach(rooms, id: \.self) { room in
                    Text(room)
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	When we change the counter for a room, then select a different picker value, we never see the &lt;code&gt;RoomView&lt;/code&gt; update: it always will show the initial room (“Main Room”). Why does this happen?
&lt;/p&gt;
&lt;p&gt;
	In our &lt;code&gt;RoomView1&lt;/code&gt;‘s initializer, we’re not actually changing the value of the state property. &lt;em&gt;When we assign to a state property outside of the view’s &lt;code&gt;body&lt;/code&gt;, we are changing the initial value that’s used when that view is created in the &lt;a href=&quot;https://talk.objc.io/episodes/S01E438-attribute-graph-part-10&quot;&gt;attribute graph&lt;/a&gt;.&lt;/em&gt;
	 You can only modify the state’s value inside the body of a view.
&lt;/p&gt;
&lt;p&gt;
	This is why I have that personal rule of always making the state property as private (so no one can assign from the outside) and initializing it straight away (so I’m not allowed to initialize it in the view’s &lt;code&gt;init&lt;/code&gt;). And yet: we cannot do this for our example above.
&lt;/p&gt;
&lt;h2&gt;
	Towards a Fix
&lt;/h2&gt;
&lt;p&gt;
	There’s no one perfect way to solve this, but here’s one approach. Because we have a dependency on &lt;code&gt;name&lt;/code&gt; in our view model expression, we also need to add an &lt;code&gt;onChange(of:)&lt;/code&gt;. Each time the name changes, we create a new view model.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct RoomView2: View {
    var name: String
    @State var viewModel: RoomVM
    init(name: String) {
        self.name = name
        self.viewModel = RoomVM(roomName: name)
    }

    var body: some View {
        LabeledContent(viewModel.roomName) {
            Stepper(&amp;quot;\(viewModel.count)&amp;quot;, value: $viewModel.count)
        }
        .onChange(of: name) {
            self.viewModel = RoomVM(roomName: name)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Another way to think about this is that the &lt;code&gt;name&lt;/code&gt; uniquely determines the identity of our &lt;code&gt;RoomView2&lt;/code&gt;. When that name changes, the identity changes and we should recreate our view model. The code above works as expected in all scenarios. It is not quite there yet, though. After I published this article, Kyle asked the following question:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		@chris In the fixed example, is it correct to say that an instance of the view model object will be needlessly created each time the view’s initializer is called by the parent? As in: when you change the selected room in the parent, the child’s initializer is run, name is set to the new name, the view model object is created and then not used (because of the attribute graph as you mentioned), and then the “original” view model is replaced by a third inside of onChanged?
	&lt;/p&gt;
	&lt;p&gt;&lt;a href=&quot;https://m.objc.io/@kyle@mister.computer/114461147374335228&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	Kyle is absolutely right. The example above does have the correct behavior, but unnecessarily creates and discards new objects everytime the initializer runs.
&lt;/p&gt;
&lt;h2&gt;
	A Final Solution?
&lt;/h2&gt;
&lt;p&gt;
	Here’s a variant that creates the &lt;code&gt;RoomVM&lt;/code&gt; once when the view appears, and only recreates when the &lt;code&gt;name&lt;/code&gt; property changes:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct RoomView3: View {
    var name: String
    @State private var viewModel: RoomVM?

    var body: some View {
        ZStack {
            if let viewModel {
                RoomView3Helper(viewModel: viewModel)
            }
        }
        .onChange(of: name, initial: true) {
            self.viewModel = RoomVM(roomName: name)
        }
    }
}

struct RoomView3Helper: View {
    @Bindable var viewModel: RoomVM

    var body: some View {
        LabeledContent(viewModel.roomName) {
            Stepper(&amp;quot;\(viewModel.count)&amp;quot;, value: $viewModel.count)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	The code is way less clean than what we started with, but at least it’s correct and efficient. I wonder if there’s an cleaner way to write the code above, or if this is really what we need to resort to.
&lt;/p&gt;
&lt;h2&gt;
	Conclusion
&lt;/h2&gt;
&lt;p&gt;
	One of the hardest parts about this problem is that, initially, our code seemed to work correctly. It seemed to just do the right thing. It’s hard to catch this problem during testing, but as long as you stick to the private/initial value rules, you’ll never have that problem. If you do need to break the rule, pay extra attention and add on &lt;code&gt;onChange(of:)&lt;/code&gt; for every property that your view model depends on.
&lt;/p&gt;
&lt;h2&gt;
	Updates
&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;May 7&lt;/em&gt;
	: Added a version that doesn’t recreate the model every single time the `init runs.
&lt;/p&gt;
			</description>
			<pubDate>Tue, 06 May 2025 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/swiftui-view-model</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/swiftui-view-model</guid>
		</item>
		<item>
			<title>SwiftUI Alignment Guide Bug</title>
			<description>
				&lt;p&gt;
	For the last few years SwiftUI’s &lt;a href=&quot;https://www.swiftuifieldguide.com/layout/alignment/&quot;&gt;alignment guides&lt;/a&gt; have been broken in combination with &lt;code&gt;if&lt;/code&gt;-statements. Here’s a small example:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    var value = true
    var body: some View {
        Color.green
            .frame(width: 100, height: 100)
            .overlay(alignment: .topLeading) {
                if true {
                    Circle()
                        .alignmentGuide(.leading) { $0.width/2 }
                }
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	&lt;picture class=&quot;swiftui&quot;&gt;
		&lt;source media=&quot;(prefers-color-scheme: dark)&quot; srcset=&quot;http://chris.eidhof.nl/post/swiftui-alignment-guide-bug/1-dark.png 2x&quot;/&gt;
		&lt;img srcset=&quot;http://chris.eidhof.nl/post/swiftui-alignment-guide-bug/1.png 2x&quot; style=&quot;width: auto;&quot;/&gt;
	&lt;/picture&gt;
&lt;/p&gt;
&lt;p&gt;
	I expected the horizontal center of the circle to be aligned with the leading edge of the green color. Instead, both views are centered on top of each other. When you remove the &lt;code&gt;if&lt;/code&gt; it works as expected. If you replace the &lt;code&gt;if&lt;/code&gt; with an &lt;code&gt;if/else&lt;/code&gt; it’s still broken, but if you replace the &lt;code&gt;if&lt;/code&gt; with a &lt;code&gt;switch&lt;/code&gt; it does work as expected.
&lt;/p&gt;
&lt;p&gt;
	Depending on your use case you can also replace the &lt;code&gt;if&lt;/code&gt; statement with an opacity modifier or something similar.
&lt;/p&gt;
&lt;p&gt;
	(&lt;em&gt;FB13676056&lt;/em&gt;
	 for Apple folks. Last tested on Xcode 16.3).
&lt;/p&gt;
			</description>
			<pubDate>Mon, 05 May 2025 00:00:00 +0200</pubDate>
			<link>http://chris.eidhof.nl/post/swiftui-alignment-guide-bug</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/swiftui-alignment-guide-bug</guid>
		</item>
		<item>
			<title>Bindings</title>
			<description>
				&lt;p&gt;
	In SwiftUI, there are two &lt;em&gt;kinds&lt;/em&gt;
	 of bindings. There are bindings created using keypaths, and then there are bindings created using &lt;code&gt;Binding(get:set:)&lt;/code&gt;. These are not the same at all.
&lt;/p&gt;
&lt;p&gt;
	For example, consider the following (contrived) view:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    @State var value1 = false
    @State var value2 = false
    var body: some View {
        VStack {
            Toggle(&amp;quot;Test&amp;quot;, isOn: $value1)
            Nested(value2: $value2)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	When we change either &lt;code&gt;value1&lt;/code&gt; or &lt;code&gt;value2&lt;/code&gt;, the &lt;code&gt;body&lt;/code&gt; of &lt;code&gt;ContentView&lt;/code&gt; will re-render. This is expected. However, we only want &lt;code&gt;Nested&lt;/code&gt; to re-render its body when &lt;code&gt;value2&lt;/code&gt; changes, not when &lt;code&gt;value1&lt;/code&gt; changes.
&lt;/p&gt;
&lt;p&gt;
	Under the hood, SwiftUI looks at the &lt;code&gt;Nested&lt;/code&gt; value in the attribute graph and compares it against the new value of &lt;code&gt;Nested&lt;/code&gt; that we’re constructing above. If these are “the same”, it will not re-render the body of &lt;code&gt;Nested&lt;/code&gt;. It is not documented how this comparison works, but Javier &lt;a href=&quot;https://swiftui-lab.com/equatableview/&quot;&gt;found out some things&lt;/a&gt; and there are some old tweets by SwiftUI team members. SwiftUI compares the old and new view on a field-by-field basis, and only if all fields are the same, it stops re-rendering.
&lt;/p&gt;
&lt;p&gt;
	In the example above, SwiftUI can do this comparison, and indeed, the &lt;code&gt;body&lt;/code&gt; of &lt;code&gt;Nested&lt;/code&gt; will not be re-rendered unless the actual value of &lt;code&gt;value2&lt;/code&gt; changed.
&lt;/p&gt;
&lt;h2&gt;
	Manual Binding Problems
&lt;/h2&gt;
&lt;p&gt;
	Instead of writing &lt;code&gt;$value2&lt;/code&gt;, we could have also constructed a &lt;em&gt;manual binding&lt;/em&gt;
	 using &lt;code&gt;Binding(get: { value2 }, set: { value2 = $0 })&lt;/code&gt;. This has a different behavior: every time &lt;code&gt;ContentView&lt;/code&gt; re-renders its body, &lt;code&gt;Nested&lt;/code&gt; will re-render its body as well. Even when only &lt;code&gt;value1&lt;/code&gt; changes. While we don’t have access to the SwiftUI source code, I think the problem is that these manual bindings store closures instead of a “pointer” to the state value. Every time the body of &lt;code&gt;ContentView&lt;/code&gt; executes, a new closure is constructed. Swift cannot compare closures and thus SwiftUI needs to re-render the body of &lt;code&gt;Nested&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
	While the above is a contrived example (no one would write &lt;code&gt;Binding(get:set:)&lt;/code&gt; instead of &lt;code&gt;$value2&lt;/code&gt;) the distinction between binding types becomes important when creating member bindings. For example, in &lt;a href=&quot;http://chris.eidhof.nl/post/swiftui-binding-tricks/&quot;&gt;SwiftUI Binding Tricks&lt;/a&gt; we look at creating a binding of &lt;code&gt;Bool&lt;/code&gt; from a state property of type &lt;code&gt;Set&lt;/code&gt;. While it might be easier to write this using a &lt;code&gt;Binding(get:set:)&lt;/code&gt; we do create a potential efficiency trap. When you write something like &lt;code&gt;$selection[contains: element]&lt;/code&gt; a binding with a keypath is constructed, and SwiftUI can compare these kinds of bindings effectively. At least one of the companies we’ve worked with has documentation and warnings in place because using &lt;code&gt;Binding(get:set:)&lt;/code&gt; caused way too many view body redraws.
&lt;/p&gt;
&lt;h2&gt;
	Conclusion
&lt;/h2&gt;
&lt;p&gt;
	I think we should avoid &lt;code&gt;Binding(get:set:)&lt;/code&gt; in production code. In most cases, you will probably not see a big difference in performance, but it can come back to bite you. With some practice, bindings using key paths rather than &lt;code&gt;Binding(get:set:)&lt;/code&gt; are just as easy to write and often simplify testing.
&lt;/p&gt;
&lt;h2&gt;
	References
&lt;/h2&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;
			Jacob Van Order wrote an article where he &lt;a href=&quot;https://jacobvanorder.github.io/swiftui-bindings-digging-a-little-deeper/&quot;&gt;measures the different approaches&lt;/a&gt; using the SwiftUI Instruments templates.
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
			</description>
			<pubDate>Fri, 21 Mar 2025 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/binding-with-get-set</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/binding-with-get-set</guid>
		</item>
		<item>
			<title>Why I Avoid Group</title>
			<description>
				&lt;p&gt;
	In &lt;a href=&quot;https://www.swiftuifieldguide.com/workshops/&quot;&gt;our SwiftUI workshops&lt;/a&gt;, we often see people reaching for the &lt;code&gt;Group&lt;/code&gt; view. There’s a lot of code out there that does this, and yet, I noticed myself avoiding &lt;code&gt;Group&lt;/code&gt;, even though it can be pretty handy. In investigating, I realized that it’s not even &lt;code&gt;Group&lt;/code&gt; that is the problem. It seems to be the meeting point of SwiftUI and UIKit.
&lt;/p&gt;
&lt;p&gt;
	In my understanding, &lt;code&gt;Group&lt;/code&gt; is just a way to get view builder syntax, but doesn’t really add any “structure” or “container” node.
&lt;/p&gt;
&lt;p&gt;
	In SwiftUI, when you have an &lt;code&gt;if/else&lt;/code&gt; statement and want to apply a modifier to that, it won’t compile:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;if let image {
    Image(image)
} else {
    Text(&amp;quot;Loading…&amp;quot;)
}
.onAppear {  } /* does not compile */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	You can fix this by wrapping everything in a &lt;code&gt;Group&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;Group {
    if let image {
        Image(image)
    } else {
        Text(&amp;quot;Loading…&amp;quot;)
    }
}
.onAppear {  } /* works */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	So far, so good. And yet, every time I see this it makes me uneasy, because &lt;code&gt;Group&lt;/code&gt; has such strange, unpredictable behavior. For some reason, it always seems to come back and bite me. However, I couldn’t really put my finger on it. In this post, I’ve boiled down the problem, so that in the future, I have an explanation that I can &lt;a href=&quot;https://simonwillison.net/2024/Jul/13/give-people-something-to-link-to/&quot;&gt;link to&lt;/a&gt;.
&lt;/p&gt;
&lt;h2&gt;
	Group Variadics
&lt;/h2&gt;
&lt;p&gt;
	You can also use &lt;code&gt;Group&lt;/code&gt; to apply some modifiers to &lt;em&gt;each&lt;/em&gt;
	 of the subviews rather than to the group as a whole. For example, you can apply padding and a background to each of the elements in a &lt;code&gt;Group&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;Group {
    Text(&amp;quot;One&amp;quot;)
    Text(&amp;quot;Two&amp;quot;)
}
.padding()
.background(.blue)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	If you create a new Xcode project and add this as the &lt;code&gt;body&lt;/code&gt; of your &lt;code&gt;ContentView&lt;/code&gt;, it will look like this in the previews:
&lt;/p&gt;
&lt;p&gt;
	&lt;picture class=&quot;swiftui&quot;&gt;
		&lt;source media=&quot;(prefers-color-scheme: dark)&quot; srcset=&quot;http://chris.eidhof.nl/post/why-i-avoid-group/1-dark.png 2x&quot;/&gt;
		&lt;img srcset=&quot;http://chris.eidhof.nl/post/why-i-avoid-group/1.png 2x&quot; style=&quot;width: auto;&quot;/&gt;
	&lt;/picture&gt;
&lt;/p&gt;
&lt;p&gt;
	However, if you then run that very same app in the Simulator, it looks very different:
&lt;/p&gt;
&lt;p&gt;
	&lt;picture class=&quot;swiftui&quot;&gt;
		&lt;source media=&quot;(prefers-color-scheme: dark)&quot; srcset=&quot;http://chris.eidhof.nl/post/why-i-avoid-group/3-dark.png 2x&quot;/&gt;
		&lt;img srcset=&quot;http://chris.eidhof.nl/post/why-i-avoid-group/3.png 2x&quot; style=&quot;width: auto;&quot;/&gt;
	&lt;/picture&gt;
&lt;/p&gt;
&lt;p&gt;
	The complete difference in behavior above is the reason why I avoid &lt;code&gt;Group&lt;/code&gt;.  From my interpretation, the &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/group&quot;&gt;Group documentation page&lt;/a&gt; makes it clear that the preview behavior is correct, and the Simulator behavior is a bug.
&lt;/p&gt;
&lt;p&gt;
	Some modifiers do seem to work differently. In the official documentation, it says:
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		The modifier applies to all members of the group — and not to the group itself. For example, if you apply onAppear(perform:) to the above group, it applies to all of the views produced by the if isLoggedIn conditional, and it executes every time isLoggedIn changes.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
	In my testing, I saw a different behavior, it only called &lt;code&gt;onAppear&lt;/code&gt; once. If I understand the document correctly, the code below would print twice (and yet it doesn’t):
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;Group {
    Text(&amp;quot;One&amp;quot;)
    Text(&amp;quot;Two&amp;quot;)
}
.onAppear {
    print(&amp;quot;Appear&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	It seems that the behavior not only differs between simulator and previews, but also between different modifiers.
&lt;/p&gt;
&lt;h2&gt;
	Aside: View Lists
&lt;/h2&gt;
&lt;p&gt;
	Before we look at the problem, let’s consider some theory. If we look at the definition of (say) &lt;code&gt;HStack&lt;/code&gt;, we’ll see that it’s generic over a single &lt;code&gt;Content&lt;/code&gt; parameter that conforms to &lt;code&gt;View&lt;/code&gt;. Looking at the type, we could say that an &lt;code&gt;HStack&lt;/code&gt; has a single subview. But clearly we know that an &lt;code&gt;HStack&lt;/code&gt; has multiple subviews!
&lt;/p&gt;
&lt;p&gt;
	The &lt;code&gt;HStack&lt;/code&gt; receives a single type that conforms to the &lt;code&gt;View&lt;/code&gt; protocol, but it can &lt;em&gt;flatten&lt;/em&gt;
	 that into a list of subviews. For example, the two texts in the group above turn into a &lt;code&gt;TupleView&amp;lt;(Text, Text)&amp;gt;&lt;/code&gt;. The HStack can flatten the tuple view to get a list of the two subviews. A few years ago I wrote more about how &lt;a href=&quot;https://chris.eidhof.nl/post/swiftui-views-are-lists/&quot;&gt;SwiftUI Views are Lists&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
	When a flattened view list turns out to be a single item it’s called a &lt;em&gt;unary view&lt;/em&gt;
	, and if it’s zero or more items, it’s a &lt;em&gt;multiview&lt;/em&gt;
	. You can also read about this in &lt;a href=&quot;https://movingparts.io/variadic-views-in-swiftui&quot;&gt;Robb’s post&lt;/a&gt; or my own post on &lt;a href=&quot;https://chris.eidhof.nl/post/variadic-views/&quot;&gt;variadic views&lt;/a&gt;.
&lt;/p&gt;
&lt;h2&gt;
	Investigating the Problem
&lt;/h2&gt;
&lt;p&gt;
	At first, I thought the problem was with &lt;code&gt;Group&lt;/code&gt;. But it seems to be a problem with the “root view” that renders a SwiftUI view. I believe (but haven’t verified) that ultimately, at the very root of our app, there is still some UIKit that renders our root view. If that root view is not a &lt;em&gt;unary view&lt;/em&gt;
	, the behavior can be unexpected.
&lt;/p&gt;
&lt;p&gt;
	For example, with the code below, the root view is not unary but actually returns two views:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;Group {
    Text(&amp;quot;One&amp;quot;)
    Text(&amp;quot;Two&amp;quot;)
}
.padding()
.background(.blue)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	We can see the exact same behavior difference between previews and the Simulator when we replace the &lt;code&gt;Group&lt;/code&gt; with an explicit view builder:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    var body: some View {
        helper
            .padding()
            .background(.blue)
    }

    @ViewBuilder var helper: some View {
        Text(&amp;quot;One&amp;quot;)
        Text(&amp;quot;Two&amp;quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	To fix the differences in behavior, we can just wrap our &lt;code&gt;body&lt;/code&gt; in a &lt;code&gt;VStack&lt;/code&gt;. This way, the &lt;code&gt;VStack&lt;/code&gt; is the new, stable, unary root view and SwiftUI will have no problems rendering this as expected:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;VStack {
    helper
        .padding()
        .background(.blue)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Wrapping in a &lt;code&gt;VStack&lt;/code&gt; works with both the view builder variant as well as the &lt;code&gt;Group&lt;/code&gt;, which seems to confirm that &lt;code&gt;Group&lt;/code&gt; is really just a way to get view builder syntax, nothing more.
&lt;/p&gt;
&lt;blockquote&gt;
	&lt;p&gt;
		Note: The &lt;code&gt;onAppear&lt;/code&gt; problem still exists: if you replace the padding and background with an &lt;code&gt;onAppear&lt;/code&gt;, it’ll still only get called once for the entire group. At least this behavior is consistent between the Simulator and previews, and between &lt;code&gt;Group&lt;/code&gt; and view builders.
	&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
	Other Possible Problems
&lt;/h2&gt;
&lt;p&gt;
	If my hunch is correct, it might be a problem where UIKit meets SwiftUI. There are actually a few places where this happens. Many of the builtin components still use UIKit under the hood and could be a candidate for this behavior. For example, let’s try navigation stacks:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;NavigationStack {
    Group {
        Text(&amp;quot;One&amp;quot;)
        Text(&amp;quot;Two&amp;quot;)
    }
    .padding()
    .background(.blue)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	This renders as expected: the modifiers are applied to the items and not to the group as a whole.
&lt;/p&gt;
&lt;p&gt;
	Sheets are broken, though, ImageRenderer is broken and UIHostingView doesn’t work either.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;Text(&amp;quot;Hello&amp;quot;)
    .sheet(isPresented: .constant(true)) {
        Group {
            Text(&amp;quot;One&amp;quot;)
            Text(&amp;quot;Two&amp;quot;)
        }
        .padding()
        .background(.blue)
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Again, for these broken cases you can fix the behavior by having a stable unary view as the root. I haven’t tested all of the framework, but I expect there to be more broken UIKit-wrapping containers.
&lt;/p&gt;
&lt;h2&gt;
	Conclusion
&lt;/h2&gt;
&lt;p&gt;
	I think the behavior of &lt;code&gt;Group&lt;/code&gt; (or to be more precise: applying modifiers to lists of views) is just too unreliable to use in production. Why does it differ between the Simulator and previews? Why does &lt;code&gt;onAppear&lt;/code&gt; on a list get called once, but the background gets applied to each item?
&lt;/p&gt;
&lt;p&gt;
	For me, I’m avoiding &lt;code&gt;Group&lt;/code&gt; where possible, and always choose for “stable containers” such as a stack (&lt;code&gt;VStack&lt;/code&gt; and &lt;code&gt;ZStack&lt;/code&gt; are my favorite, for some strange reason, &lt;code&gt;HStack&lt;/code&gt; feels wrong). Going back to the initial example, I would write it like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;VStack {
    if let image {
        Image(image)
    } else {
        Text(&amp;quot;Loading…&amp;quot;)
    }
}
.onAppear {  } /* works */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	Note: all of this is tested with iOS 18.2, hopefully some of this will be fixed in the future.
&lt;/p&gt;
			</description>
			<pubDate>Wed, 19 Mar 2025 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/why-i-avoid-group</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/why-i-avoid-group</guid>
		</item>
		<item>
			<title>SwiftUI Phase Animation &quot;Bug&quot;</title>
			<description>
				&lt;p&gt;
	I noticed that phase animators weren’t behaving as expected, and I initially assumed I’d found a bug in SwiftUI. It took me way too long to realize the problem here, that’s why I am writing this up. Spoiler alert: &lt;em&gt;there is no bug&lt;/em&gt;
	.
&lt;/p&gt;
&lt;p&gt;
	I am preparing a workshop on SwiftUI Animations (this is a follow-up to our regular &lt;a href=&quot;https://www.swiftuifieldguide.com/workshops/&quot;&gt;SwiftUI Workshop&lt;/a&gt;). As I went through the exercises, I created a very minimal shake animation to demo how phase animators work.
&lt;/p&gt;
&lt;p&gt;
	A phase animator lets you animate between multiple phases (in the example below, the initial phase is 0, the second phase is 20 and the third phase -20). It starts by displaying the view at its initial phase. When we change the trigger value, it animates to the second phase. When that animation completes, it animates to the third phase. Finally, it animates back to the initial phase. This is essentially a really nice way to run nested animations in completion handlers. In the animation closure, you get the &lt;em&gt;target phase&lt;/em&gt;
	 in and can choose which animation curve you want to animate towards that value.
&lt;/p&gt;
&lt;p&gt;
	Here’s my attempt at trying to create a shake animation with a custom timing curve for the first part of the animation. I exaggerated the curve so that it’s really clear that this doesn’t work:
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    @State var trigger = 0
    var body: some View {
        Button(&amp;quot;Hello&amp;quot;) {
            trigger += 1
        }.phaseAnimator([0, 20, -20], trigger: trigger) {
            $0.offset(x: $1) // somehow always animates with the default animation
        } animation: { phase in
            switch phase {
            case 20: .linear(duration: 5)
            default: .default
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	If you run the example above, you can even set a breakpoint and see that the &lt;code&gt;linear&lt;/code&gt; animation gets used. What’s more, you can add a &lt;code&gt;transaction { print($0.animation) }&lt;/code&gt; to the content closure, and you’ll see the correct animation printed out. Yet it does not animate slowly.
&lt;/p&gt;
&lt;p&gt;
	The problem has two causes:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;
			SwiftUI buttons have an implicit animation going on. This happens even with a custom &lt;code&gt;ButtonStyle&lt;/code&gt;. I haven’t verified this, but I think it animates when you depress a button (when &lt;code&gt;isPressed&lt;/code&gt; changes back to false).
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;
			Layout modifiers such as &lt;code&gt;offset&lt;/code&gt; are applied at the leaf nodes in the view tree (in this case, the actual button). In other words, the &lt;code&gt;offset&lt;/code&gt; itself does not animate, but instead, the &lt;code&gt;x&lt;/code&gt; position of the button animates.
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	As far as I’m aware, the button animation behavior is not documented. The layout behavior is underdocumented and could be much clearer. There are a number of solutions: the easiest is to apply a &lt;code&gt;geometryGroup&lt;/code&gt; before the &lt;code&gt;offset&lt;/code&gt;. This causes the offset to apply to the group as a whole, rather than being applied at the leaf views. Interestingly, the documentation of &lt;code&gt;geometryGroup&lt;/code&gt; actually explains that position and size are set at the leaf views.
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;swift&quot;&gt;struct ContentView: View {
    @State var trigger = 0
    var body: some View {
        Button(&amp;quot;Hello&amp;quot;) {
            trigger += 1
        }.phaseAnimator([0, 20, -20], trigger: trigger) {
            $0
                .geometryGroup() // this now animates as a whole rather than at the leaf views
                .offset(x: $1)
        } animation: { phase in
            switch phase {
            case 20: .linear(duration: 5)
            default: .default
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
	The geometry group above now animates its frame and uses the animation it receives from the current transaction (the five second linear animation). Internally, the button animates its pressed state but that doesn’t influence the position anymore.
&lt;/p&gt;
&lt;p&gt;
	Alternatively, for my purposes (demoing phase animators) I could use an &lt;code&gt;onTapGesture&lt;/code&gt; instead of a button, as I don’t need all the extra stuff that button provides (highlighting, styling and accessibility). Or use a different modifier than &lt;code&gt;offset&lt;/code&gt;, such as rotation or scale (these don’t apply at the leaf nodes).
&lt;/p&gt;
&lt;p&gt;
	This is not the first time I’ve been bitten by the default button animations, and probably not the last. Hopefully writing this post will help me remember in the future.
&lt;/p&gt;
			</description>
			<pubDate>Mon, 03 Mar 2025 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/swiftui-phase-animation-bug</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/swiftui-phase-animation-bug</guid>
		</item>
		<item>
			<title>Weeknotes № 52</title>
			<description>
				&lt;p&gt;
	Just to be consistent I’ll add the notes for this week as well. Mostly had family time and managed to put in a few longer but very cold bike rides.
&lt;/p&gt;
&lt;p&gt;
	We took the train to The Netherlands, the bike rides here are cold as well. I rented a gravel bike which is really nice (even though the brakes don’t really work all that well, so I’ll bring my own bike next time).
&lt;/p&gt;
&lt;p&gt;
	I’ve spent a bit of time on preparing upcoming workshops, we recorded another Swift Talk episode and I played around a bit with Helix. For Helix, I installed &lt;a href=&quot;https://github.com/artempyanykh/marksman&quot;&gt;Marksman&lt;/a&gt; to get nice Markdown autocompletion. It supports wiki-like syntax so that I can edit my Zettelkasten notes as well (although I prefer the more integrated workflow of The Archive).
&lt;/p&gt;
			</description>
			<pubDate>Mon, 30 Dec 2024 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2024-52</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2024-52</guid>
		</item>
		<item>
			<title>2024 in Review</title>
			<description>
				&lt;p&gt;
	This was a fun and busy year that flew by. At a personal level, a lot has happened (the oldest started school, we went on a bicycle holiday, and I started therapy again.)
&lt;/p&gt;
&lt;p&gt;
	Work-wise, the biggest thing is that I started a new company in addition to objc.io. This is the new home for our workshops as well as new projects.
&lt;/p&gt;
&lt;p&gt;
	The first project under the new company is the &lt;a href=&quot;https://www.swiftuifieldguide.com&quot;&gt;SwiftUI Field Guide&lt;/a&gt;. This is a website that tries to show how the SwiftUI layout system works by providing interactive examples. A lot of work went into it, and I’m very proud of how it turned out. I essentially reimplemented (a small part of) SwiftUI in TypeScript. It’s what I wished SwiftUI documentation would be like: way more visual and interactive. I went into all kinds of rabbit holes in order to build this. For example, just trying to implement gradients (which are used in one or two places) sent me off deep into color space land where I learned about &lt;a href=&quot;https://bottosson.github.io/posts/oklab/&quot;&gt;OKlab&lt;/a&gt; and other things.
&lt;/p&gt;
&lt;p&gt;
	I gave presentations in Hamburg, Prague, Cupertino, Paris and Bologna. They focused on the SwiftUI Layout system as well as animations. For the newer presentations, I used a new tool that lets me record me typing (during preparation) and play it back smoothly (during the actual presentation) while making sure previews are directly visible. This has been a lot of fun to work on. I used that same tool in my recent videos on Bluesky and Mastodon to give one-minute overviews of SwiftUI topics.
&lt;/p&gt;
&lt;p&gt;
	I held workshops at companies like DKB, Rossmann, Dexcom, Etsy, Wallapop, Sketch, Atlassian, Amex and StarFinanz. The learning has been tremendous from all sides – obviously the goal of a workshop is that the attendees learn, yet I learn a lot as well. It’s been really great doing more in-person workshops this year.
&lt;/p&gt;
&lt;p&gt;
	Except for Cupertino I traveled everywhere by train: London, Paris, Bologna and within Germany. It felt good to do that and I was able to use a lot of that time productively (I find that much harder when traveling by car or plane).
&lt;/p&gt;
&lt;p&gt;
	I moved to &lt;a href=&quot;https://jj-vcs.github.io/jj/latest/&quot;&gt;jujutsu&lt;/a&gt; over the last months as my main way of interacting with Git, although I still love &lt;a href=&quot;https://retcon.app&quot;&gt;Retcon&lt;/a&gt;. Both of these make rewriting history a lot simpler. With jujutsu, even the mental model is greatly simplified compared to regular git, which really helps me. Beyond that, I haven’t used a lot of new technology: I did write a lot of TypeScript in VSCode which was okay but I still strongly prefer Swift and Xcode. As an experiment, I’m using the helix editor to write this post, I do feel it has the potential to be a vim replacement for me. One new workflow I’ve gotten used to is using the Zettelkasten note taking strategy (using &lt;a href=&quot;https://zettelkasten.de/the-archive/&quot;&gt;The Archive&lt;/a&gt;).
&lt;/p&gt;
&lt;p&gt;
	At objc.io, we recorded weekly Swift Talk episodes and moved our office from Berlin to Fürstenberg. Our Berlin studio had water damage that the landlord wasn’t interested in fixing, and after half a year we had enough of that. For me, it also means I can actually cycle to and work from an office, which feels nice (I’ve worked from home for almost all of the last twenty years).
&lt;/p&gt;
&lt;p&gt;
	Out of all the code I wrote this year, maybe the &lt;a href=&quot;https://github.com/chriseidhof/dynamic-type&quot;&gt;Dynamic Type&lt;/a&gt; package has been the simplest and most useful. I’m using this package in my presentations, in our Workshop app and for a number of internal tools. It’s been pretty incredible to just have iOS-like dynamic type (including things like ScaledMetric) everywhere. During a presentation, I can increase or decrease the font size. When I’m demoing the workshop app in a conference room at a client, I can just scale things up and down where needed.
&lt;/p&gt;
&lt;p&gt;
	Ever since getting back from our cycling holiday I’ve had a foot injury, I think because I walked on flip-flops too much. While I am now mostly pain-free, I still have issues when running. Somewhere towards the end of summer, I switched to cycling, mostly gravel but some road cycling as well. Running has been a massive part of my identity for the last twenty years, but it’s been surprisingly easy and fun to replace it with cycling. I’m not quite sure what 2025 will look like exercise-wise. If I can keep up the cycling, I do want to do a longer bikepacking trip.
&lt;/p&gt;
&lt;p&gt;
	Here’s to a fun and productive 2025!
&lt;/p&gt;
			</description>
			<pubDate>Tue, 24 Dec 2024 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2024-review</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2024-review</guid>
		</item>
		<item>
			<title>Weeknotes № 51</title>
			<description>
				&lt;p&gt;
	Last week I finished almost all preparation for the first workshop of next year. There was a weird crash in the Workshop app. This app has evolved a lot over the last few years, and I didn’t quite understand the reason it was crashing (duplicate symbols?). I did remove the last remaining static framework by changing it into a Swift package and that finally got rid of the crash.
&lt;/p&gt;
&lt;p&gt;
	We researched more attribute graph stuff for &lt;a href=&quot;https://talk.objc.io&quot;&gt;Swift Talk&lt;/a&gt;. There are six episodes already and we could easily do ten more, but not sure yet where we’ll draw the line. It’d definitely be fun to do state management, the environment, preferences, etc.
&lt;/p&gt;
&lt;p&gt;
	I thought more about some patterns that I’ve used over the years: defunctionalization, explicit witnesses and incremental programming systems. I also have some more ideas written down about the incremental static site generator I’d like to build. I experimented a bit with some of those topics, hopefully I’ll be able to at least write about some of that.
&lt;/p&gt;
&lt;p&gt;
	I created four more one-minute videos:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ldftzlvzxc2m&quot;&gt;Basic Animations&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ldiqmhcsys2z&quot;&gt;Phase Animations&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ldkwkcr5322k&quot;&gt;Keyframe Animations&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ldnv7ikcdk2z&quot;&gt;Animatable&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	I’ve been thinking a bit about how to build a workflow system in Swift. This is a system that would have long-running tasks (think days or months) with discrete steps that also include user-input. For example: an email arrives, triggers a new workflow instance, some automated stuff happens but before a reply gets sent I get to review and edit it. I’d like to come up with a simple core model for describing these workflows, but it turns out to not be all that easy. I think I’ll ultimately have to go for a graph structure as I’d want forks and joins in the workflows. In writing about it just now, I realized I should start with a simpler (non-persistent) implementation to test out what the API would feel like.
&lt;/p&gt;
&lt;p&gt;
	I started reading “Stories Sell” by Matthew Dicks. So far it seems quite interesting.
&lt;/p&gt;
&lt;p&gt;
	I went for two longer gravel rides on the weekend: one was 53km and the other one 86km. It’s definitely more challenging in low temperatures, on the longer one I even got rained on quite a bit. I did bring an extra jacket and a thermos flask of tea, next time I’ll bring a spare pair of dry gloves as well. I rode some beautiful new (to me) trails.
&lt;/p&gt;
			</description>
			<pubDate>Mon, 23 Dec 2024 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2024-51</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2024-51</guid>
		</item>
		<item>
			<title>Weeknotes № 50</title>
			<description>
				&lt;p&gt;
	Last week I again tried to not focus too much on work, and instead went to Berlin to meet friends and get my passport renewed. I also played my first theatre role in almost thirty years. The previous role was at the closing play of primary school, this time it was at the youngest’s day care.
&lt;/p&gt;
&lt;p&gt;
	I put out new videos last week with a focus on alignment:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lcuimlkuhs2a&quot;&gt;Aligning stacks, frames and overlays&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lcwqelejhs2b&quot;&gt;First Text Baseline alignment&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lczc2omr622z&quot;&gt;Explicit Alignment Guides&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ld3zsqzi6s2y&quot;&gt;Sizing of ZStack vs overlay&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3ld6jo2xk5s22&quot;&gt;Aligning custom icons&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	I am still going strong on the Zettelkasten note taking method. My note with attribute graph questions is getting quite long, hopefully at some point I’ll be able to ask someone about it.
&lt;/p&gt;
&lt;p&gt;
	I read a bit about &lt;a href=&quot;http://peterfadde.com/Research/Deliberate_Performance-PI-1011.pdf&quot;&gt;deliberate performance&lt;/a&gt;. In short, you can increase performance by:
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;strong&gt;Experimenting&lt;/strong&gt;
			. Form hypotheses and these these out.
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;strong&gt;Estimating&lt;/strong&gt;
			. How long will this take me? How big / expensive will this thing take me?
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;strong&gt;Extrapolition&lt;/strong&gt;
			. How can you apply the learned things to other areas? How does this concept generalize?
		&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;
			 Writing, teaching, etc.
		&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	It’s fun to see how we apply these in our workshops as well. We give a quick intro, let the attendees solve a solution, have them explain their solution and then try to discuss the theory or what it means in the abstract.
&lt;/p&gt;
&lt;p&gt;
	I read more about &lt;a href=&quot;https://www.pathsensitive.com/2019/07/the-best-refactoring-youve-never-heard.html&quot;&gt;defunctionalization&lt;/a&gt;. We also &lt;a href=&quot;https://www.objc.io/blog/2019/09/10/defunctionalization/&quot;&gt;wrote about this&lt;/a&gt; in 2019. It’s been a pattern I keep seeing everywhere: in the Elm Architecture, in TCA and while thinking about building virtual machines (you can start with a naive functional implementation of whatever it is you want to build and then use CPS + defunctionalization to build a very efficient iterative implementation).
&lt;/p&gt;
&lt;p&gt;
	I’ve been thinking about using the stuff I learned about the attribute graph implementation to take yet another stab at an incremental static site generator (just for fun). Not sure when I’ll have time for that.
&lt;/p&gt;
			</description>
			<pubDate>Mon, 16 Dec 2024 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2024-50</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2024-50</guid>
		</item>
		<item>
			<title>Weeknotes № 49</title>
			<description>
				&lt;p&gt;
	Last week was more personal stuff and less work. I spent a morning teaching a zine workshop to primary school kids, which was really fun. One of our kids was sick and I had to take care of her for a day, which only left three short days of work time. I spent an evening rehearsing a short play with other parents for the day care. I think the last time I was in a play was almost thirty years ago.
&lt;/p&gt;
&lt;p&gt;
	Because of weather and responsibilities, I cycled indoors quite a bit (instead of in the forest). This is definitely better than running on the treadmill (which I used to do a few years ago). A lot more comfortable and not as loud.
&lt;/p&gt;
&lt;p&gt;
	I recorded short videos and posted them on Bluesky and Mastodon (hope to keep doing that for the rest of December):
&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lccp3fl2xs2z&quot;&gt;How to use Flexible Frames&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lcf64ao7vk2n&quot;&gt;Image Resizing and Aspect Ratio&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lchrl3jtbs2v&quot;&gt;Scaled Metric&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
	&lt;li&gt;
		&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/eidhof.nl/post/3lcmrzsuyl22u&quot;&gt;Text&lt;/a&gt;&lt;/p&gt;
	&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
	I spent more time figuring out how the attribute graph works. At the same time, I tried porting some of GraphViz’s layout algorithm to SwiftUI.
&lt;/p&gt;
&lt;p&gt;
	I had a call with Nathan from &lt;a href=&quot;https://retcon.app&quot;&gt;Retcon&lt;/a&gt;. We talked about a bunch of stuff, but one quote really stuck with me: “when designing puzzles in games, the designers want people to try the wrong solution first”. I always feel anytime we make a mistake and later do it right, learning is much better. Maybe I could use this as inspiration for my teaching.
&lt;/p&gt;
			</description>
			<pubDate>Mon, 09 Dec 2024 00:00:00 +0100</pubDate>
			<link>http://chris.eidhof.nl/post/2024-49</link>
			<guid isPermaLink="true">http://chris.eidhof.nl/post/2024-49</guid>
		</item>
	</channel>
</rss>