CoverLetterGen is a native iPad application that generates cover letters using the OpenAI API. It automates the formatting and drafting process while ensuring that personally identifiable information (PII) remains local to the device.
- Language: Swift 6
- Architecture: MVVM
- UI Framework: SwiftUI
- Persistence: SwiftData
- Concurrency: Swift Structured Concurrency (Async/Await, Actors)
- External API: OpenAI (Responses Endpoint)
To prevent PII leakage, the application separates generation from data assembly:
- Generation: The
OpenAIServicesends only the anonymized resume text and job description to the LLM. - Assembly: The
AppViewModelreceives the raw text and prepends the user's contact details (name, address, phone) locally. - Storage: User profile data is persisted in
UserDefaults, while generated content is stored inSwiftData.
The project adopts strict concurrency checking to ensure thread safety:
- Actor Isolation:
OpenAIServiceis defined as anactorto serialize network requests and manage session state. - Main Actor:
AppViewModelis isolated to@MainActor, ensuring all state mutations and UI updates occur on the main thread without manual dispatching.
Data persistence is handled by SwiftData:
- Schema: The
CoverLettermodel stores the generated text along with the configuration state used at the time of generation (TextLengthOption,TextToneOption). - State Restoration: Selecting a historical item re-initializes the application state (Input fields, Tone, Length) to match that record, enabling context switching.
Networking is implemented using URLSession and custom Codable structs.
- Schema Validation: The client maps the nested response structure of the GPT-5.2 API (
output->content->text) to strongly typed models. - Testing: Deterministic tests are implemented using a
MockURLProtocol, allowing the service to be tested without live API calls.
File: OpenAIService.swift
Parsing the nested response structure from the Responses API.
let decodedResponse = try JSONDecoder().decode(ResponsesResponse.self, from: data)
// Parse nested structure: output[0] -> content[0] -> text
if let firstOutput = decodedResponse.output.first,
let textContent = firstOutput.content.first(where: { $0.type == "output_text" })?.text {
return textContent
}File: AppViewModel.swift
Restoring configuration state from a persisted model. The usage of Enums prevents invalid state.
var selectedLetter: CoverLetter? {
didSet {
if let letter = selectedLetter {
resumeInput = letter.resumeText
jobInput = letter.jobDescription
// Restore Settings from persisted raw values
if let l = TextLengthOption(rawValue: letter.lengthOption) { length = l }
if let t = TextToneOption(rawValue: letter.toneOption) { tone = t }
}
}
}- Clone the repository.
- Open
CoverLetterGen.xcodeproj(Xcode 26.2+). - Run on a simulator or device.
- Configure the OpenAI API Key in the application settings.
MIT
