O que PropMotion faz?
Faça um objeto 3D atuar em seu app SwiftUI. Padrões de produção para um trabalho específico e comum: um ator de produto polido em um palco SceneKit transparente — entradas, saídas, arremessos, sombras, feedback tátil e as armadilhas silenciosas que custam dias.
Quando usar SceneKit
Seja honesto sobre a posição do framework:
- SceneKit está em modo de manutenção. Para novos apps com necessidades 3D pesadas (pipelines de assets, USD/USDZ, AR, mundos grandes) prefira RealityKit.
- SceneKit ainda é o caminho mais rápido para um ator 3D decorativo dentro de um app SwiftUI: uma
SCNViewtransparente se compõe sobre qualquer layout SwiftUI, modificadores de shader de geometria são uma única string MSL, a interoperabilidade com CoreAnimation é madura, e tudo aqui roda em views UIKit simples, sem configuração de sessão. - Se o elemento 3D é um único objeto herói com movimento coreografado, as receitas deste skill se aplicam diretamente. Se é um mundo interativo completo, pare e considere RealityKit primeiro.
A receita central do palco
Um palco é: SCNView transparente, um coordenador @MainActor que possui o grafo de cena, uma câmera na altura dos olhos, um rig de três luzes mais uma luz de sombra dedicada, uma mancha de contato e um coletor de sombras. O ator atua através de animações de quadro-chave pré-definidas.
struct ProductStage: UIViewRepresentable {
let item: Item
// A chave deve mudar SOMENTE quando a cena deve mudar visivelmente.
private var stateKey: String { "\(item.id)" }
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> SCNView {
let view = SCNView()
view.backgroundColor = .clear // o palco se compõe sobre SwiftUI
view.isOpaque = false
view.antialiasingMode = .multisampling4X
view.scene = context.coordinator.buildScene()
view.pointOfView = context.coordinator.cameraNode
context.coordinator.install(item)
context.coordinator.markState(stateKey)
// Aquecimento do primeiro frame, ignição de duas chaves: compilar shaders fora
// do caminho crítico, estacionar o ator fora do palco, e portar a primeira
// entrada tanto em um atraso mínimo quanto na conclusão do prepare.
context.coordinator.parkOffstage()
if let scene = view.scene {
view.prepare([scene]) { _ in
DispatchQueue.main.async { context.coordinator.markPipelinesWarm() }
}
}
context.coordinator.scheduleFirstEntrance()
return view
}
func updateUIView(_ view: SCNView, context: Context) {
// Diferenciação por chave de estado: re-renderizações do SwiftUI nunca devem repetir entradas.
guard context.coordinator.stateKey != stateKey else { return }
context.coordinator.markState(stateKey)
context.coordinator.transition(to: item)
}
}
As peças, na ordem de construção:
- Flags de view transparente (
backgroundColor = .clear,isOpaque = false), MSAA 4x. A tela hospedeira fornece o fundo; a cena não tem caixa. - O coordenador possui a hierarquia de nós, decomposta em um nó por preocupação de movimento (viagem, elevação, guinada, rotação), para que animações independentes nunca disputem uma única transformação.
- Câmera:
projectionDirection = .horizontalpara que o tamanho do ator siga a largura do palco; posição elevada com uma leve inclinação para baixo parece um observador em pé. Mantenha-a estática; uma câmera em movimento parece sintética. - Luzes: chave quente, preenchimento frio baixo, aro duro, ambiente apenas como valor base, mais uma luz direcional separada que projeta sombra, apontada de trás para cima do ator para que nunca perturbe a escultura visível.
- Contato com o chão: um plano de gradiente escuro suave (a mancha de contato) sob o ator, mais um plano coletor invisível
.shadowOnlypara a sombra real. - Materiais reflexivos amostram um pequeno mapa de ambiente programático, não uma foto. Mantenha a zona atrás da câmera escura.
- Toda coreografia é
CAKeyframeAnimationpré-definida, protegida por tokens de geração para que qualquer nova batida suplante o trabalho pendente.
Detalhes completos com código: references/stage-recipe.md
As armadilhas que mais custam tempo
| Armadilha | Correção |
|---|---|
| Sombras adiadas nunca renderizam quando MSAA está ligado, sem erros no console | Use shadowMode = .forward mais um coletor .shadowOnly. Depure qualquer sombra ausente primeiro dando à cena um chão Lambert cinza visível. |
O primeiro frame de uma SCNView nova compila pipelines Metal no meio da sua animação de entrada | prepare([scene]) em segundo plano, estacione o ator fora do palco, atrase a primeira entrada. Nunca inicie uma animação no frame um. |
fillMode = .forwards + isRemovedOnCompletion = false prende a apresentação, e a remoção é por objeto | Um clearAnimations central que varre o nó, cada geometria filha, as luzes e as ações em execução, chamado de todos os pontos de entrada. |
| Metal real de frente renderiza como um buraco cinza ou preto | Um espelho visto de frente reflete a zona do ambiente atrás da câmera. Mantenha a base pintada aprovada e adicione uma camada aditiva fina com seu próprio mapa de reflexão. |
Alternar castsShadow faz aparecer uma penumbra desfocada em um frame; shadowBias e o categoryBitMask da luz são ignorados para sombras direcionais dianteiras | Mantenha castsShadow permanentemente ligado e anime o alfa de shadowColor, sincronizado com o movimento. |
| Um atraso fixo de aquecimento ainda engasga em dispositivos frios e no Simulador; o primeiro frame de um efeito de partículas compila seu próprio pipeline e cai exatamente quando dispara | Ignição de duas chaves: porte a entrada no atraso mínimo E no manipulador de conclusão do prepare. Aqueça os pipelines de partículas com uma rajada de opacidade zero correspondendo às flags do efeito real. |
O formato padrão do UIGraphicsImageRenderer herda a escala da tela, inflando cada textura gerada em 9x em pixels — mortal para texturas renderizadas ao vivo (gravação por tecla) | Fixe a escala do formato do renderizador em 1 e dimensione a tela para a projeção na tela do ator; coalesça redirecionamentos de múltiplas entradas em uma renderização por passo de atualização. |
| Reproduzir o movimento de um objeto real a partir de fotos produz rigs confiantes que falham lateralmente — cada correção revela um novo problema | Fotos trazem poses, não caminhos ou mecanismos; ajustes de pose final não determinam a trajetória. Modele o caminho: um rig de calibração com controles de pose diretos, o proprietário criando quadros-chave contra o objeto físico. |
| Um movimento de quadro-chave trepida ritmicamente em seus quadros-chave e sobrevive a todas as correções de renderização e temporização | Os solavancos estão embutidos na curva: a base Catmull-Rom uniforme em quadros-chave espaçados irregularmente altera a velocidade em cada nó. Interpole com tangentes Hermite ponderadas por intervalo (ou uma cúbica natural) sobre fases baseadas em distância, e porte em uma verificação de continuidade numérica. |
| Uma sub-malha cortada de um modelo maior mede como se fosse o objeto inteiro, sem erro algum | O corte cortou apenas o buffer de índices; o buffer de vértices ainda contém cada vértice do original. Meça apenas os vértices referenciados pelos índices da sub-malha. |
Uma imagem quadrada atribuída a scene.lightingEnvironment é silenciosamente ignorada — zero reflexões, todo material de espelho renderiza preto, sem saída no console | Pinte o mapa de ambiente em um layout de cubemap reconhecido, mais fácil uma tela esférica 2:1 (1024x512). Apenas material.reflective aceita um mapa esférico quadrado. |
Mapa de referência
- references/stage-recipe.md: o palco completo, configuração da view, coordenador, hierarquia de nós, câmera, rig de iluminação, mancha de contato e coletor, mapas de ambiente programáticos e texturas, partículas de impacto em um palco, modificadores de shader em atores do palco (a armadilha do uniforme de espaço linear), mantendo a identidade SwiftUI do palco (armadilhas de remontagem e tempestade de atualização).
- references/first-frame-and-warmup.md: o engasgo de compilação de pipeline Metal no primeiro desenho e ambas as curas, aquecimento com entrada atrasada (atualizado para ignição de duas chaves portada na conclusão do prepare), ou manter a cena montada aquecida e redirecioná-la; aquecimento de pipeline de partículas; provando a cura com sinais e rastreios de engasgo no dispositivo.
- references/shadows-and-lights.md: toda armadilha silenciosa de sombra, a combinação confiável forward + shadowOnly, animando a visibilidade da sombra, o orçamento de luz neutro, metal de frente.
- references/baked-animation.md: por que quadros-chave pré-definidos vencem temporizadores, a folha de dicas para projetar batidas multifásicas antes de assá-las, classificação de emendas (C1 vs impulsos de contato), projetando peso, amostragem densa, contabilidade de limpeza, tokens de geração, roubando um nó no meio da animação, rolando sem deslizar, rolando ao longo de caminhos no chão (direção, palco no espaço da tela, trilhas de depuração), canais além das transformações (pesos de morfing, valores de lente, uniforms de shader), molas como material de autoria, uma propriedade um dono.
- references/motion-from-reference.md: reproduzindo o movimento de um objeto real — o que fotos e vídeo podem e não podem lhe dizer, modelando o caminho em vez do mecanismo, o rig de calibração (o proprietário poseia o ator e salva quadros-chave), parametrização de distância e interpolação ponderada por intervalo de poses salvas manualmente, a armadilha Catmull-Rom uniforme, portões de continuidade numérica, loops de estado cosseno contínuos com saídas da fase atual, medindo sub-malhas cortadas.
- references/physics-without-engine.md: o integrador de passo fixo artesanal assado em quadros-chave, paredes e chão como números simples, feedback tátil orientado por contato, uma tomada de pivô de borda, e por que isso supera SCNPhysics para cenas coreografadas.
- references/gestures-and-haptics.md: pan-to-grab sem teste de acerto, clamps suaves enquanto segurado, velocidade de soltura, feedback tátil de impacto, batidas programadas sobrevivendo a dedos vivos (retentativas limitadas, pesquisas com token, guardas de gestos para ator oculto, roubo fecha o contrato de retenção), Reduce Motion como taxonomia, acesso VoiceOver a um palco invisível, coexistindo com gestos SwiftUI.
- references/sequencing-stages.md: dirigindo vários palcos como um filme — um relógio mestre com batidas como dados, armadilhas de deriva de tempo real, cortes em movimento após a saída limpar, pré-montagem do próximo palco para aquecimento, um palco sobrevivendo ao seu próprio corte (fumaça persistente), rebaseando a linha do tempo em uma interação do usuário, uma liderança de gravação, verificando cortes quadro a quadro.
- references/modeling-actors.md: construindo objetos heróis críveis a partir de primitivas — proporções do mundo real antes de olhar, anéis para faces recuadas, silhuetas de tubo mais toro, legibilidade de padrão radial, inclinação por instância para faces côncavas, lacunas abertas, recursos de relevo como geometria (nunca pintura), albedo de metal acetinado em palcos escuros, portando atores que chegam como arquivos.
- references/camera-choreography.md: a câmera como o performer — o rig de órbita, voos de revelação pré-definidos, foco seguindo o dolly, arrastar-órbita com inércia e voo para casa, reflexões SCNFloor como aterramento, palco fosco sob luzes diretas, restrições para rigs de rastreamento (e por que eles nunca vão no ator).
- references/instancing-and-swarms.md: dezenas de atores de uma vez — uma geometria para o enxame, pilhas baseadas em slot em vez de física, parábolas resolvidas de trás para frente a partir do ponto de aterrissagem, tombamento mesclado a uma pose de descanso, aleatoriedade com semente, escalonamento por agendamento, notas de orçamento.
- references/multi-actor-physics.md: vários atores em uma simulação assada — N vetores de estado em um relógio, colisões em pares (separe, depois troque ao se aproximar), o congelar-o-mundo, testando qual ator o dedo pegou, listas de contato por ator para esmagamento e feedback tátil.
Lista de verificação de revisão
Antes de enviar um palco, verifique:
-
backgroundColor = .cleareisOpaque = falsena SCNView; o palco se compõe sem caixa ou costura visível. - Nenhuma animação começa no primeiro frame da view;
prepareexecuta e a primeira entrada é atrasada ou a cena é mantida montada aquecida. - Sombras usam modo forward com um coletor
.shadowOnly; nada depende de sombras adiadas,shadowBiasoucategoryBitMaskda luz. - Planos com textura alfa (rótulos, decalques) têm
castsShadow = false, ou projetarão sombras quadradas. - Mudanças de visibilidade de sombra rampeiam o alfa de
shadowColor;castsShadownunca é alternado em tempo de execução. - Se a cena deve renderizar uma textura com fidelidade de cor, ambiente mais direcional vezes cos(incidência) soma neutro (1000) para uma superfície plana; sem cinzas claros cortados.
- Qualquer imagem em
lightingEnvironment(oubackground) usa um layout de cubemap reconhecido — esférico 2:1, faixa 6:1 ou 1:6, ou seis imagens; uma imagem quadrada ali é silenciosamente ignorada e materiais reflexivos renderizam preto. - Toda continuação assíncrona (entradas atrasadas, feedback tátil agendado, transições encadeadas) captura um token de geração e o re-verifica antes de agir.
- Qualquer código que rouba um nó animando captura os valores
.presentationno modelo antes de chamarremoveAllAnimations. - Animações pré-definidas definem o modelo para a pose final antes de
addAnimation; nada depende defillMode = .forwardssem uma varredura de limpeza. - Batidas multifásicas têm um comentário de folha de dicas acima da função de pose que corresponde ao struct de limite de fase; tempos de fase e dicas vivem nesse único struct, nunca como literais no loop de amostra ou no feedback tátil agendado. Toda emenda é classificada: emendas de design são C1, emendas de impulso ocorrem apenas em eventos de contato e cada uma acerta uma dica.
- Objetos rolantes escravizam a rotação à distância dividida pelo raio; nada desliza.
- Movimento copiado de um objeto real é criado como quadros-chave através de um rig de calibração (controles de pose diretos, poses salvas, o olho do proprietário),
...