Escolha SwiftUI Canvas quando o arraste exigir regras explícitas de posicionamento que você possa implementar por conta própria. Escolha SpriteKit quando as formas precisarem empurrar, quicar ou continuar se movendo por meio da física. Comece decidindo o que acontece quando uma forma arrastada encontra outra.

Este guia trata de uma única tarefa: escolher a arquitetura de interação para um app de Mac com círculos ou retângulos sintéticos, mantidos na memória, que os usuários arrastam dentro de uma área de trabalho controlada pelo app. A decisão depende do comportamento das colisões, do encaminhamento das entradas e de qual componente controla a posição de cada forma.

O que deve acontecer quando duas formas se encontram?

“Resposta à colisão” pode descrever vários requisitos diferentes. Destacar uma sobreposição, recusar uma posição inválida e fazer dois objetos quicarem exigem mecanismos distintos. A Apple separa explicitamente os contatos do SpriteKit, que informam quando os corpos se tocam, das colisões, que calculam uma resposta física e aplicam impulsos. About Collisions and Contacts

Comportamento desejado Ponto de partida prático Responsabilidade que permanece com você
Destacar uma sobreposição enquanto o ponteiro posiciona uma forma livremente Canvas com testes de geometria personalizados Seleção, detecção de sobreposição e feedback visual
Parar uma forma no primeiro obstáculo Canvas com restrições de movimento personalizadas Testar o trajeto do movimento e determinar a posição aceita
Empurrar formas vizinhas e permitir que parem ou quique SpriteKit Configuração da física e um controlador de arraste
Dar a cada item controles e gestos comuns do SwiftUI Views individuais do SwiftUI Regras específicas do app para sobreposição ou movimento

Estas são recomendações de arquitetura derivadas dos recursos documentados, não limites de desempenho definidos pela Apple. Uma quantidade pequena de formas não favorece automaticamente o Canvas, e uma quantidade grande não exige automaticamente o SpriteKit. Primeiro escolha o comportamento; depois meça a implementação com uma quantidade representativa de itens.

Há também uma restrição de interação importante: o acompanhamento exato do ponteiro e a ausência estrita de sobreposições podem entrar em conflito. Se o ponteiro passar por uma posição ocupada, o app precisa permitir que o item se sobreponha, pará-lo atrás do ponteiro ou mover outros itens. Especifique essa escolha antes de selecionar o mecanismo de renderização.

O que o Canvas oferece e o que você precisa criar?

O Canvas oferece desenho em modo imediato por meio de um contexto gráfico. Seus círculos, caminhos e símbolos não se tornam elementos interativos independentes. A Apple também declara que o Canvas não oferece acessibilidade para elementos desenhados individualmente, inclusive símbolos fornecidos como views. Portanto, um gesto anexado ao Canvas precisa de lógica do app para determinar qual item o usuário selecionou. Canvas

Para um editor com regras explícitas de posicionamento, mantenha as formas em um modelo e trate o desenho como uma projeção desse modelo. Cada item pode conter um identificador, um centro, dimensões e uma ordem de desenho. As diretrizes do Swift recomendam estruturas por padrão para dados comuns e classes quando são necessárias identidade compartilhada ou herança de um framework. Um modelo de itens baseado em tipos por valor e uma subclasse de cena do SpriteKit atendem a propósitos diferentes. Choosing Between Structures and Classes

Um arraste no Canvas pode seguir esta sequência:

  1. No início do gesto, teste a posição inicial do ponteiro contra os itens na ordem inversa de desenho e selecione o primeiro correspondente.
  2. Mantenha o identificador selecionado e seu centro original durante todo o gesto.
  3. Calcule cada centro proposto a partir do centro original mais a translação total do gesto.
  4. Aplique os limites da área de trabalho e as regras de colisão para obter um centro aceito.
  5. Atualize o modelo; deixe o Canvas redesenhá-lo.
  6. Limpe a seleção quando o gesto terminar e defina a limpeza para interações interrompidas.

A distinção entre movimento total e incremental é importante. O SwiftUI define translation como o movimento desde o início do gesto. Somar esse total repetidamente a um centro já atualizado acumula movimento demais. Use o centro inicial mantido a cada vez. O SwiftUI expõe startLocation, location e translation pelo valor do gesto e permite especificar o espaço de coordenadas do gesto. DragGesture.Value, DragGesture

Mantenha a seleção estável durante o arraste. Repetir a operação inicial de seleção a cada atualização do ponteiro pode trocar o item selecionado quando o ponteiro cruzar outra forma. Da mesma forma, preservar o deslocamento inicial entre o ponteiro e o centro evita que a forma salte de repente para ficar com o centro sob o ponteiro. Essas são regras de projeto do app, não comportamentos fornecidos automaticamente pelo Canvas.

Exemplo prático: detecção de sobreposição versus bloqueio do movimento

Considere dois círculos ilustrativos, cada um com raio de 20 pontos. O círculo A começa em (100, 100) e o círculo B, parado, está em (200, 100). O usuário solicita que o centro de A fique em (190, 100). A distância entre os centros seria de 10 pontos; a soma dos raios é 40. Essa posição proposta causa uma sobreposição.

Para indicar a sobreposição, aceite a posição proposta e altere o desenho. Em um arraste horizontal bloqueado, a primeira posição de contato é (160, 100): nesse ponto, os centros ficam a 40 pontos um do outro. Esse cálculo pressupõe que B permaneça parado, que os dois círculos mantenham o mesmo tamanho e que A siga o trajeto horizontal.

Agora suponha que uma atualização do ponteiro solicite (260, 100). Testar apenas esse ponto final não detecta sobreposição, embora o trajeto do movimento passe por B. Uma implementação que bloqueie o arraste precisa de um teste de interseção ao longo do trajeto ou de outro método explícito para verificar o caminho intermediário. Rejeitar apenas pontos finais sobrepostos não implementa a regra “parar no primeiro obstáculo”.

Este exemplo é o principal motivo para definir a resposta à colisão com precisão. Alguns testes de geometria dos pontos finais podem bastar para implementar um recurso que destaca sobreposições. Oferecer deslizamento ao redor de obstáculos, contatos simultâneos ou movimento de itens vizinhos amplia consideravelmente o trabalho de criar um mecanismo próprio. O Canvas continua sendo uma opção de desenho, mas esse comportamento adicional fica sob responsabilidade do app.

Quando o SpriteKit se torna a melhor opção?

O SpriteKit se torna atraente quando a resposta à colisão deve surgir de uma simulação. Associar um SKPhysicsBody a um nó habilita cálculos que envolvem gravidade, atrito e colisões; a simulação então atualiza as posições e orientações dos nós. Ele oferece corpos de volume para objetos e corpos de borda para limites. SKPhysicsBody

Para um primeiro protótipo, use dois círculos separados e um limite retangular para a área de trabalho. Atribua aos círculos corpos circulares correspondentes, desative a gravidade para criar uma área de trabalho semelhante a uma mesa e decida se a rotação contribui para a interação. Configure o atrito, a restituição e o amortecimento deliberadamente, em vez de depender dos valores padrão. Essas propriedades determinam aspectos diferentes do comportamento físico. SKPhysicsBody

O SwiftUI pode hospedar a cena diretamente usando SpriteView, que a Apple documenta como uma view do SwiftUI que renderiza uma cena do SpriteKit. A disponibilidade documentada para Mac começa no macOS 11; a do Canvas começa no macOS 12. Portanto, as versões mínimas do sistema também fazem parte da decisão, embora o comportamento da interação continue sendo o principal critério. SpriteView, Canvas

Configure a resposta à colisão separadamente das notificações

Atribua categorias às formas e ao limite; em seguida, configure explicitamente as duas máscaras de interação:

  • categoryBitMask identifica as categorias às quais um corpo pertence.
  • collisionBitMask seleciona as categorias que podem afetar fisicamente esse corpo.
  • contactTestBitMask solicita notificações de contato para as categorias selecionadas.

As máscaras de colisão são avaliadas de forma independente para cada corpo. Para obter uma resposta mútua comum entre duas formas dinâmicas, configure ambos os corpos para responder à categoria do outro. A comunicação de contatos segue uma regra diferente: uma comparação correspondente entre categoria e máscara de contatos em qualquer direção pode gerar uma notificação. Ativar as notificações por si só não configura o bloqueio físico. collisionBitMask, contactTestBitMask

Se os corpos responderem fisicamente, mas nenhum retorno de chamada for recebido, verifique a máscara de contatos e o delegado de contatos do mundo físico. Se houver retornos de chamada enquanto as formas atravessam umas às outras, examine as máscaras de colisão e o controlador de movimento. Essas verificações separam a configuração das notificações do problema de movimento propriamente dito.

Quem controla a posição durante o arraste?

Um arraste no SpriteKit precisa de um controlador explícito. O mecanismo de física, por si só, não acompanha o ponteiro. Escolha entre posicionamento direto e movimento controlado pela simulação conforme o comportamento definido anteriormente.

No posicionamento direto, o código do app atribui a posição do nó arrastado. Isso é adequado quando o ponteiro precisa posicionar o item com exatidão, mas não cria por si só um trajeto resolvido fisicamente entre posições sucessivas. Desativar isDynamic faz o corpo ignorar forças e impulsos; isso não cria um mecanismo de arraste. Se você alterar temporariamente essa propriedade, defina quando ela será restaurada e se, ao soltar, o movimento deve ser mantido ou zerado. isDynamic

No arraste controlado pela simulação, mantenha a posição do ponteiro como alvo e use forças ou um controlador baseado em mola para orientar um corpo dinâmico até ele. A simulação pode então resolver os contatos enquanto o corpo se move. A contrapartida é um atraso intencional ou um deslocamento em relação ao ponteiro quando outro objeto obstrui o movimento. O SpriteKit permite aplicar forças, e SKPhysicsJointSpring conecta dois corpos físicos com comportamento de mola. Esses são blocos de construção que exigem ajustes específicos para cada app. SKPhysicsBody, SKPhysicsJointSpring

Evite atribuir continuamente as posições do ponteiro enquanto espera que a física controle a mesma posição de forma independente. A sequência de quadros documentada pelo SpriteKit executa atualizações do app, ações, física e retornos de chamada posteriores em etapas separadas. Uma regra prática de controle é deixar a entrada atualizar o alvo do arraste, a lógica da cena aplicar o controlador e a física produzir a posição resultante do corpo. Responding to Frame-Cycle Events

Os retornos de chamada de contato também têm um limite documentado. A Apple afirma que eles são executados durante a etapa de simulação física, quando não é possível modificar o mundo físico, e que alterações nos corpos simulados têm comportamento indefinido. Registre o trabalho pendente no retorno de chamada e aplique as alterações físicas durante a atualização seguinte, seguindo o padrão de sinalização e atualização da Apple. SKPhysicsContactDelegate

Como a entrada do macOS deve atravessar os limites entre frameworks?

Escolha um único responsável pelas entradas da área de trabalho. Uma implementação com Canvas pode anexar um DragGesture do SwiftUI à sua superfície de interação. Uma superfície personalizada do AppKit pode processar eventos do responder; mouseDragged(with:) recebe o movimento com o botão esquerdo do mouse pressionado. Evite fazer com que um gesto do SwiftUI sobreposto e um manipulador do AppKit movam o mesmo item selecionado de forma independente. DragGesture, mouseDragged(with:)

Para fazer a ponte entre AppKit e SpriteKit, converta as coordenadas em cada limite. NSEvent.locationInWindow normalmente é expresso no sistema de coordenadas base da janela associada. Converta esse ponto para as coordenadas da view hospedeira e, em seguida, das coordenadas da view para as coordenadas da cena. A Apple fornece convertPoint(fromView:) para a última etapa e exige que a cena seja apresentada antes de chamá-lo. A posição de um nó é expressa no sistema de coordenadas do seu nó pai; portanto, nós aninhados podem exigir outra conversão. locationInWindow, convertPoint(fromView:), position

Use SpriteView para uma integração direta com o SwiftUI. Se precisar de uma view personalizada do AppKit, NSViewRepresentable oferece criação, atualização, coordenação e finalização. Deixe o SwiftUI controlar o layout da view representada: a Apple alerta explicitamente que você não deve atribuir diretamente o frame ou os limites gerenciados por ele. NSViewRepresentable

Mantenha a cena em um proprietário estável, em vez de criar uma nova sempre que o SwiftUI recalcular o conteúdo da view. No Canvas, mantenha o modelo dos itens da mesma forma. A documentação de modelos do SwiftUI explica como o estado gerenciado preserva uma instância quando a view é recriada; a abordagem Observation exige o macOS 14 ou posterior. Trate os comandos da cena e os instantâneos do modelo como transferências deliberadas, para que duas cópias mutáveis não disputem o controle das posições. Managing model data in your app

O que você deve testar antes de se comprometer com uma das abordagens?

Use uma pequena área de trabalho sintética para testar as interações difíceis antes de adicionar refinamentos visuais:

  1. Segure perto da borda de uma forma e confirme que o deslocamento inicial é preservado.
  2. Arraste lentamente até outro item e repita com um único movimento amplo do ponteiro que passe por cima dele.
  3. Arraste em direção a um canto onde haja duas formas vizinhas.
  4. Solte enquanto toca em outro item e verifique o comportamento esperado ao parar.
  5. Redimensione a área de trabalho e verifique a seleção, os limites e a conversão de coordenadas.
  6. Recrie o conteúdo SwiftUI ao redor e confirme que as posições dos itens persistem.
  7. Interrompa um arraste e confirme que a seleção e as configurações físicas temporárias são limpas.

No SpriteKit, ative SKView.showsPhysics para verificar se a geometria dos corpos corresponde à área de colisão pretendida. Para corpos pequenos que se movem rapidamente, avalie usesPreciseCollisionDetection; a Apple documenta seu custo computacional maior e sua função na detecção de colisões que verificações comuns baseadas em quadros podem não detectar. Isso não deve substituir um controlador de arraste coerente. SKView, usesPreciseCollisionDetection

Escolha Canvas quando suas regras de posicionamento forem claras, limitadas e mais fáceis de expressar diretamente. Escolha SpriteKit quando a interação física entre formas fizer parte da tarefa. Em qualquer uma das implementações, um arraste confiável no desktop começa com um único item selecionado, conversões explícitas de coordenadas e um componente responsável por cada atualização de posição.

Fontes