一键导入
view-model
Cria um novo ViewModel SwiftUI seguindo o padrao MVVM do projeto (iOS 15+). Use quando o usuario pedir para criar um novo ViewModel.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Cria um novo ViewModel SwiftUI seguindo o padrao MVVM do projeto (iOS 15+). Use quando o usuario pedir para criar um novo ViewModel.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Cria uma nova View SwiftUI seguindo o padrao MVVM do projeto (iOS 15+). Use quando o usuario pedir para criar uma nova tela, view ou screen.
Implementa animacoes SwiftUI seguindo as melhores praticas de performance e corretude. Use quando o usuario pedir para animar views, criar transicoes, usar phaseAnimator, keyframeAnimator, ou matchedGeometryEffect.
Implementa efeitos Liquid Glass (iOS 26+) com fallbacks corretos para versoes anteriores. Use APENAS quando o usuario pedir explicitamente para adotar Liquid Glass, glass effects, glassEffect modifier, ou o novo visual design do iOS 26.
Implementa funcionalidades específicas de macOS em apps SwiftUI — Scenes (Settings, MenuBarExtra, WindowGroup, Window, UtilityWindow, DocumentGroup), estilos de janela e toolbar, NavigationSplitView, Inspector, Commands, HSplitView, Table, operações de arquivo, drag-drop entre apps, e interop AppKit. Use quando o usuário precisar de qualquer API exclusiva ou com comportamento diferenciado no macOS.
Cria uma nova Model (struct) seguindo os padroes MVVM do projeto SwiftUI. Use quando o usuario pedir para criar um novo modelo, entidade ou struct de dados.
Configura a navegacao SwiftUI seguindo o padrao MVVM do projeto (iOS 15+). Use quando o usuario pedir para criar o AppRouter, adicionar rotas, configurar NavigationView, ou implementar navegacao programatica.
| name | view-model |
| description | Cria um novo ViewModel SwiftUI seguindo o padrao MVVM do projeto (iOS 15+). Use quando o usuario pedir para criar um novo ViewModel. |
Crie um novo ViewModel SwiftUI seguindo o padrao MVVM do projeto. Target minimo: iOS 15.
Nome em PascalCase (sem o sufixo ViewModel): $ARGUMENTS
Se $ARGUMENTS estiver vazio, pergunte: "Qual o nome do ViewModel? (ex: ProductDetail, Profile)"
A partir do nome PascalCase, derive:
<Name>ViewModel para o nome da classeViewModels/<Name>ViewModel.swiftUserRepository, ProductRepository) — se nao souber, use um placeholder[User], Product, Void) — determina o ViewState<T>load()Todo ViewModel usa o enum ViewState para representar o estado atual da tela. Isso garante que a View sempre saiba exatamente em qual estado se encontra, sem depender de combinacoes de booleans.
ViewStateCrie o arquivo Utilities/ViewState.swift caso ainda nao exista no projeto:
import Foundation
enum ViewState<T> {
case idle
case loading
case success(T)
case error(String)
var isLoading: Bool {
if case .loading = self { return true }
return false
}
var value: T? {
if case .success(let data) = self { return data }
return nil
}
var errorMessage: String? {
if case .error(let message) = self { return message }
return nil
}
}
ViewModels/<Name>ViewModel.swiftImporte
Combineapenas se o ViewModel usar publishers reativos (ex: busca com debounce). Para a maioria dos casos,Foundatione suficiente.
import Foundation
@MainActor
final class <Name>ViewModel: ObservableObject {
// MARK: - State
@Published private(set) var state: ViewState<<DataType>> = .idle
// MARK: - Dependencies
private let repository: <Repository>Protocol
// MARK: - Init
init(repository: <Repository>Protocol = <Repository>()) {
self.repository = repository
}
// MARK: - Actions
func load() async {
state = .loading
do {
let data = try await repository.fetch()
state = .success(data)
} catch {
state = .error(error.localizedDescription)
}
}
}
A View consome o state do ViewModel com um switch:
var body: some View {
Group {
switch viewModel.state {
case .idle:
EmptyView()
case .loading:
ProgressView("Carregando...")
case .success(let data):
// TODO: Renderize o conteudo com `data`
ContentView(data: data)
case .error(let message):
ErrorStateView(message: message) {
Task { await viewModel.load() }
}
}
}
.task {
await viewModel.load()
}
}
Quando o usuario informar as acoes, use estes padroes como referencia:
@Published private(set) var state: ViewState<[<Model>]> = .idle
func loadItems() async {
state = .loading
do {
let items = try await repository.fetchAll()
state = .success(items)
} catch {
state = .error(error.localizedDescription)
}
}
func delete(_ item: <Model>) async {
guard case .success(var items) = state else { return }
do {
try await repository.delete(item)
items.removeAll { $0.id == item.id }
state = .success(items)
} catch {
state = .error(error.localizedDescription)
}
}
Requer
import Combineno arquivo.
@Published private(set) var searchState: ViewState<[<Model>]> = .idle
@Published var searchText: String = ""
private var cancellables = Set<AnyCancellable>()
// MARK: - Init
init(repository: <Repository>Protocol = <Repository>()) {
self.repository = repository
setupSearch()
}
private func setupSearch() {
$searchText
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.sink { [weak self] query in
Task { await self?.search(query: query) }
}
.store(in: &cancellables)
}
func search(query: String) async {
guard !query.isEmpty else {
searchState = .idle
return
}
searchState = .loading
do {
let results = try await repository.search(query: query)
searchState = .success(results)
} catch {
searchState = .error(error.localizedDescription)
}
}
@Published private(set) var saveState: ViewState<Void> = .idle
func save() async {
saveState = .loading
do {
// TODO: Construa o model a partir do estado do form
// let newItem = <Model>(...)
// try await repository.create(newItem)
saveState = .success(())
} catch {
saveState = .error(error.localizedDescription)
}
}
@Published private(set) var state: ViewState<[<Model>]> = .idle
@Published private(set) var hasMorePages: Bool = true
private var currentPage = 0
func loadNextPage() async {
guard hasMorePages else { return }
if case .loading = state { return }
let currentItems = state.value ?? []
state = .loading
do {
let newItems = try await repository.fetch(page: currentPage)
state = .success(currentItems + newItems)
hasMorePages = !newItems.isEmpty
currentPage += 1
} catch {
state = .error(error.localizedDescription)
}
}
Quando a tela possui acoes independentes (ex: carregar dados + salvar formulario), use propriedades ViewState separadas:
@Published private(set) var state: ViewState<[<Model>]> = .idle
@Published private(set) var saveState: ViewState<Void> = .idle
@Published private(set) var deleteState: ViewState<Void> = .idle
Informe o usuario que ainda precisa:
Utilities/ViewState.swift existe no projeto — se nao, crie-oRepositories/ (se ainda nao existir), com Protocol + implementacao concreta@StateObjectTests/ seguindo o padrao:import Testing
@testable import ProjectName
@Suite("<Name>ViewModel Tests")
struct <Name>ViewModelTests {
@Test("Estado inicial é idle")
func initialState() {
let viewModel = <Name>ViewModel(repository: Mock<Repository>())
#expect(!viewModel.state.isLoading)
#expect(viewModel.state.value == nil)
#expect(viewModel.state.errorMessage == nil)
}
@Test("Carrega dados com sucesso")
func loadSuccess() async {
let mockRepo = Mock<Repository>(state: .success)
let viewModel = <Name>ViewModel(repository: mockRepo)
await viewModel.load()
#expect(viewModel.state.value != nil)
#expect(viewModel.state.errorMessage == nil)
}
@Test("Exibe erro quando falha")
func loadFailure() async {
let mockRepo = Mock<Repository>(state: .failure)
let viewModel = <Name>ViewModel(repository: mockRepo)
await viewModel.load()
#expect(viewModel.state.errorMessage != nil)
#expect(viewModel.state.value == nil)
}
}
Foundation; adicione import Combine apenas quando usar publishers reativos (busca com debounce, etc.)ObservableObject — nunca @Observable (requer iOS 17)@MainActorfinal classViewState<T> para gerenciar estado — nunca booleans avulsos (isLoading, hasError)@Published private(set) em todas as propriedades de estado — imutavel externamenteinit com valor padrao (para testabilidade)async/await para concorrencia — prefira sobre Combine para novas features<Repository>Protocol), nao de implementacoes concretas// MARK: - seguindo a ordem: State, Dependencies, Init, ActionsViewState separadas (ex: state, saveState, deleteState)| NAO usar | Requer | Usar no lugar |
|---|---|---|
@Observable | iOS 17 | ObservableObject + @Published |
Observation framework | iOS 17 | Combine |
final class com ObservableObject (nunca struct, nunca @Observable)@MainActor aplicado na classeimport SwiftUI — apenas Foundation (+ Combine se necessario)ViewState<T> para todos os estados — sem booleans avulsos (isLoading, hasError)@Published private(set) em todas as propriedades de estadoinit com valor padrao concretoloadUsers, deleteItem, save)ViewState.error(message) — sem throws para a ViewMARK: - separando State / Dependencies / Init / ActionsViewState.swift existe em Utilities/ antes de usar ViewState<T>Mock<Repository> para sucesso, falha e estado inicial