Using Claude with SwiftUI and Figma? Well…

Prenez
4 min readSep 27, 2024

--

“It sort of works, meh.”

“It works well enough.”

Pick one, or take home both.

Part of the problem with the current state of AI is that we bring expectations that it may not be able to meet — now, if ever. Dario Amodei recently said in an interview that he regretted that the industry had landed on the idea of ‘intelligence’, because AI is not intelligent. It is capable, meaning it is a capable of certain tasks, and capable of a certain quality in delivering those tasks. These will always be factors in our human evaluations of AI.

Artificial Capability. Now that sounds about right.

~ ~ ~

I just took a Designcode.io course, Using Claude With SwiftUI. Excellent course, highly recommended, it’s short and to the point. The course demonstrated that you could take 3 Figma designs, drop them into Claude AI and engine would produce respectable SwiftUI code for 3 SwiftUI screens that you could drop in and use.

“It sort of works, ,meh.”

It produced code quickly, sure, but the code ignored most of the newest features of Swift, and used obsolete features, even after repeated attempts at correcting it. It turned into whack-a-mole. Because Claude has no memory, it wouldn’t remember its last fix, and would lose it, while fixing the new problem you had just called to its attention.

class LoginViewModel: ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
@Published var isLoginMode: Bool = false

func toggleLoginMode() {
isLoginMode.toggle()
}

func continueAction() {
// Implement login or signup logic
print("Continue tapped with email: \(email)")

So, first indication that this is your granddad’s SwiftUI code, the engine is using @ObservableObject and @Published from Combine for variables that when changed trigger updates. Apple now recommends using @Observable. Look, I get that AI is not perfect, but my issue with this is that one should have an expectation of seeing not just any old code, but even if its incorrect and needs fixing, it’s smart enough to use the latest recommended weapons.

@Observable
class LoginViewModel {
var email = ""
var password = ""
var isLoading = false
var errorMessage: String?

The interesting thing — or the incredibly irritating thing — is that it KNEW (using that word loosely) what the right solution was. It knows about the latest SwiftUI tools, but used the old tools to create code. Claude AI is capable but not intelligent — you see the difference?

“It works well enough.”

The code Claude AI produces is okay as a template if you’re a seasoned SwiftUI developer, but heaven help you if you’re a beginner and you think you can learn fom it. You and your employer will be sorely disappointed.

Next, for the View, it produced this code:

struct LoginView: View {
@State private var viewModel = LoginViewModel()

This isn’t correct either. The right way to bring an @Observable object into the View is using @Bindable, as in:

struct LoginView: View {
@Bindable var viewModel: LoginViewModel

var body: some View {
// ... rest of the view

Claude also had to be told, don’t use GCD.

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
self?.isLoading = false
// Here you would typically call your actual login API
if self?.email == "test@example.com" && self?.password == "password" {
print("Login successful")
// Handle successful login (e.g., navigate to main app screen)
} else {
self?.errorMessage = "Invalid credentials. Please try again."
}
}
}

And again, it knows about async/await, and when reminded, used it in the code it assembled (AI doesn’t ‘write’ code, but I have a feeling people will use that anyway, the way they talk about ‘printing’ 3D objects. You’re not printing them, you’re manufacturing/emitting/forming, but it’s not printing, and the thing that’s making them isn’t a printer).

    @MainActor
func login() async {
guard isInputValid else {
errorMessage = "Please enter a valid email and password."
return
}

isLoading = true
errorMessage = nil

do {
try await Task.sleep(for: .seconds(2)) // Simulating network delay
// Here you would typically call your actual login API
if email == "test@example.com" && password == "password" {
print("Login successful")
// Handle successful login (e.g., navigate to main app screen)
} else {
errorMessage = "Invalid credentials. Please try again."
}
} catch {
errorMessage = "An error occurred. Please try again."
}

And I note that it also applied @MainActor to the async function. And the good news is, it used @MainActor in the most efficient way (see link below). It turns out it’s way more efficient, generating a lot less SIL code, than the Dispatch method above. It’s also more efficient than trying to fine-tune where you put the @MainActor directive.

These problems are concerning enough, but an even more concerning error is that Claude never generates the same code twice, even if it got it right the first time. It has no memory, and it doesn’t ‘improve’ code it’s already created, instead it generates it all from scratch every time, using the same probabilistic guessing method that it used the first time. But if the probabilities change, well…it’s horseshoes and hand grenades time, because it’s ‘close enough’ to make Claude AI happy.

I’ve tried praising it, I’ve tried insulting it, I’ve tried giving it a curfew, but the AI just won’t do specifically what I tell it to do, and that’s something you have to get used to. Remember, that was one of the first things you learned about computers? If it’s doing something wrong, it’s your fault, because computers will only do specifically what you tell them to do. Not if you have a layer of AI slathered over it, as it turns out.

--

--

Prenez
Prenez

Written by Prenez

Writes iOS apps in Swift and stories in American English.

No responses yet