Deepseek kann doch SwiftUI

Ich habe ja hier behauptet das Deepseek kein Swiftui kann. Das stimmt nicht.

Anscheinend war Deepseek am Samstag nur etwas belegt, und hatte keine Kapazitäten mehr frei. Gestern habe ich es noch einmal probiert, mit dem Prompt über den Vokabeltrainer, den ich schon für den Swift Playgrounds vs. Xcode Test benötigt habe.

Folgenden Sourcecode produziert DeepSeek:

import SwiftUI

// MARK: - Datenmodell
struct Vocabulary: Identifiable, Codable, Equatable {
    let id = UUID()
    let foreignWord: String
    let answers: [String]
    let correctAnswerIndex: Int
}

// MARK: - Datenpersistenz
class VocabularyStore: ObservableObject {
    @Published var vocabularies: [Vocabulary] = []
    
    init() {
        loadVocabularies()
    }
    
    func saveVocabulary(_ vocabulary: Vocabulary) {
        vocabularies.append(vocabulary)
        saveVocabularies()
    }
    
    private func saveVocabularies() {
        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(vocabularies)
            try data.write(to: saveURL)
        } catch {
            print("Fehler beim Speichern: \(error)")
        }
    }
    
    private func loadVocabularies() {
        guard FileManager.default.fileExists(atPath: saveURL.path) else { return }
        do {
            let data = try Data(contentsOf: saveURL)
            let decoder = JSONDecoder()
            vocabularies = try decoder.decode([Vocabulary].self, from: data)
        } catch {
            print("Fehler beim Laden: \(error)")
        }
    }
    
    private var saveURL: URL {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent("VocabData")
    }
}

// MARK: - Eingabemodus
struct InputView: View {
    @EnvironmentObject private var store: VocabularyStore
    @State private var foreignWord = ""
    @State private var answers = Array(repeating: "", count: 5)
    @State private var correctAnswerIndex = 0
    
    var isFormValid: Bool {
        !foreignWord.isEmpty && answers.allSatisfy { !$0.isEmpty }
    }
    
    var body: some View {
        NavigationStack {
            Form {
                Section("Fremdwort") {
                    TextField("Wort eingeben", text: $foreignWord)
                }
                
                Section("Antwortmöglichkeiten") {
                    ForEach(0..<5, id: \.self) { index in
                        TextField("Antwort \(index + 1)", text: $answers[index])
                    }
                }
                
                Section("Korrekte Antwort") {
                    Picker("Richtige Antwort", selection: $correctAnswerIndex) {
                        ForEach(0..<5, id: \.self) {
                            Text("Antwort \($0 + 1)").tag($0)
                        }
                    }
                    .pickerStyle(.segmented)
                }
                
                Button(action: save) {
                    Label("Speichern", systemImage: "checkmark.circle")
                        .frame(maxWidth: .infinity)
                }
                .disabled(!isFormValid)
            }
            .navigationTitle("Eingabemodus")
        }
    }
    
    private func save() {
        let newEntry = Vocabulary(
            foreignWord: foreignWord,
            answers: answers,
            correctAnswerIndex: correctAnswerIndex
        )
        store.saveVocabulary(newEntry)
        foreignWord = ""
        answers = Array(repeating: "", count: 5)
        correctAnswerIndex = 0
    }
}

// MARK: - Lernmodus
struct LearnView: View {
    @EnvironmentObject private var store: VocabularyStore
    @State private var score = 0
    @State private var currentRound = 0
    @State private var currentQuestion: Vocabulary?
    @State private var shuffledAnswers: [String] = []
    @State private var correctIndex = 0
    @State private var showingResult = false
    
    var body: some View {
        VStack(spacing: 20) {
            if currentRound < 10 && !store.vocabularies.isEmpty {
                Text("Runde \(currentRound + 1)/10")
                    .font(.headline)
                
                Text("Aktuelle Punktzahl: \(score)")
                    .font(.title.bold())
                
                Text(currentQuestion?.foreignWord ?? "?")
                    .font(.largeTitle)
                    .padding(.bottom, 40)
                
                ForEach(0..<shuffledAnswers.count, id: \.self) { index in
                    Button(action: { checkAnswer(index) }) {
                        Text(shuffledAnswers[index])
                            .frame(maxWidth: .infinity)
                    }
                    .buttonStyle(.borderedProminent)
                }
            } else {
                if store.vocabularies.isEmpty {
                    Text("Keine Vokabeln vorhanden")
                        .font(.title)
                } else {
                    Text("Ergebnis: \(score) Punkte")
                        .font(.largeTitle)
                    
                    Button("Neustart") {
                        startNewGame()
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
        }
        .padding()
        .navigationTitle("Lernmodus")
        .onAppear(perform: startNewGame)
        .alert("Spiel beendet", isPresented: $showingResult) {
            Button("OK") { }
        } message: {
            Text("Finale Punktzahl: \(score)")
        }
    }
    
    private func startNewGame() {
        score = 0
        currentRound = 0
        nextQuestion()
    }
    
    private func nextQuestion() {
        guard currentRound < 10 else {
            showingResult = true
            return
        }
        
        currentQuestion = store.vocabularies.randomElement()
        guard let question = currentQuestion else { return }
        
        shuffledAnswers = question.answers.shuffled()
        correctIndex = shuffledAnswers.firstIndex(of: question.answers[question.correctAnswerIndex]) ?? 0
    }
    
    private func checkAnswer(_ index: Int) {
        score += index == correctIndex ? 1 : -1
        currentRound += 1
        nextQuestion()
    }
}

// MARK: - Hauptapp
@main
struct VokabeltrainerApp: App {
    @StateObject private var store = VocabularyStore()
    
    var body: some Scene {
        WindowGroup {
            TabView {
                InputView()
                    .tabItem { Label("Eingabe", systemImage: "plus.circle") }
                
                LearnView()
                    .tabItem { Label("Lernen", systemImage: "book.fill") }
            }
            .environmentObject(store)
        }
    }
}

Bis auf kleinere Unterschiede ist technisch kein Unterschied zur Version von ChatGPT. Wobei die ChatGPT-Version ein wenig strukturierter ist. 

Test in Playground zeigt, die Oberfläche ist ähnlich und die Funktionalität gleich.

Das Reasoning braucht allerdings bei Deepseek länger als mit dem Model o3Mini von OpenAI.