드래그에 직접 구현할 수 있는 명시적인 배치 규칙이 필요하다면 SwiftUI Canvas를 선택하세요. 도형이 물리 효과에 따라 서로 밀거나 튕기거나 계속 움직여야 한다면 SpriteKit을 선택하세요. 먼저 드래그한 도형이 다른 도형과 만날 때 어떤 일이 일어나는지 결정하세요.

이 가이드는 앱이 소유한 작업 영역 안에서 사용자가 드래그하는 합성 원이나 사각형을 메모리에 담아 사용하는 Mac 앱의 상호작용 아키텍처를 선택하는 한 가지 작업을 다룹니다. 선택은 충돌 동작, 입력 라우팅, 각 도형의 위치를 제어하는 구성 요소에 따라 달라집니다.

두 도형이 만나면 어떤 일이 일어나야 할까요?

“충돌 반응”은 여러 가지 요구 사항을 가리킬 수 있습니다. 겹침을 강조 표시하는 것, 유효하지 않은 위치를 거부하는 것, 두 객체가 튕기게 하는 것은 각각 다른 메커니즘이 필요합니다. Apple은 접촉한 물리 바디를 보고하는 SpriteKit의 접촉과 물리적 반응을 계산해 충격을 적용하는 충돌을 명확히 구분합니다. 충돌과 접촉 정보

원하는 동작 실용적인 시작점 직접 담당할 부분
포인터로 도형을 자유롭게 배치하는 동안 겹침 강조 표시 사용자 지정 기하 검사와 Canvas 선택, 겹침 감지, 시각적 피드백
첫 번째 장애물에서 도형 멈추기 사용자 지정 이동 제약이 있는 Canvas 이동 경로 검사 및 허용 위치 결정
주변 도형을 밀고 가라앉거나 튕기게 하기 SpriteKit 물리 설정 및 드래그 컨트롤러
각 항목에 일반적인 SwiftUI 컨트롤과 제스처 제공 개별 SwiftUI 뷰 앱별 겹침 또는 이동 규칙

이는 문서에 설명된 기능을 바탕으로 한 아키텍처 권장 사항이지, Apple이 제시한 성능 기준은 아닙니다. 도형 수가 적다고 해서 Canvas가 자동으로 더 적합한 것은 아니며, 도형 수가 많다고 해서 SpriteKit이 자동으로 필요한 것도 아닙니다. 먼저 원하는 동작을 선택한 다음, 실제 항목 수로 구현을 측정하세요.

유용한 상호작용상의 제약도 있습니다. 포인터를 정확히 따라가는 동작과 엄격한 비겹침 규칙은 충돌할 수 있습니다. 포인터가 점유된 위치를 지나갈 때 앱은 항목이 겹치도록 두거나, 포인터 뒤에서 멈추게 하거나, 다른 항목을 움직여야 합니다. 렌더러를 선택하기 전에 이 동작을 정하세요.

Canvas가 제공하는 기능과 직접 구현해야 하는 부분은 무엇인가요?

Canvas는 그래픽 컨텍스트를 통해 즉시 모드로 그립니다. Canvas에 그린 원, 경로, 기호는 각각 독립적인 상호작용 요소가 되지 않습니다. Apple은 뷰로 제공된 기호를 포함해 Canvas가 개별 그리기 요소에 대한 접근성 기능을 제공하지 않는다고도 명시합니다. 따라서 Canvas에 제스처를 연결하면 사용자가 어떤 항목을 선택했는지 판별하는 앱 로직이 필요합니다. Canvas

명시적인 배치 규칙이 있는 편집기에서는 도형을 모델에 저장하고, 그 모델을 화면에 투영하듯 그리세요. 각 항목에는 식별자, 중심 좌표, 크기, 그리기 순서를 담을 수 있습니다. Swift에서는 일반적인 데이터에 기본적으로 구조체를 사용하고, 공유 식별성이나 프레임워크 상속이 필요한 경우 클래스를 사용하도록 안내합니다. 값 타입 항목 모델과 SpriteKit 장면 하위 클래스는 서로 다른 용도입니다. 구조체와 클래스 중 선택하기

Canvas 드래그는 다음 순서로 처리할 수 있습니다.

  1. 제스처가 시작되면 항목을 그린 역순으로 시작 포인터 위치를 검사해 처음 일치하는 항목을 선택합니다.
  2. 제스처가 진행되는 동안 선택된 식별자와 원래 중심 좌표를 유지합니다.
  3. 매번 원래 중심 좌표에 제스처의 전체 이동량을 더해 제안 중심 좌표를 계산합니다.
  4. 작업 영역 경계와 충돌 규칙을 적용해 허용 중심 좌표를 구합니다.
  5. 모델을 업데이트하고 Canvas가 모델을 바탕으로 다시 그리게 합니다.
  6. 제스처가 끝나면 선택을 해제하고, 상호작용이 중단될 때 처리할 정리 작업도 정의합니다.

전체 이동량과 증분 이동량의 차이는 중요합니다. SwiftUI는 translation를 제스처 시작 지점부터의 이동량으로 정의합니다. 이 전체 이동량을 이미 업데이트된 중심 좌표에 계속 더하면 이동량이 누적되어 너무 멀리 움직입니다. 매번 보관해 둔 시작 중심 좌표를 사용하세요. SwiftUI는 제스처 값에서 startLocation, location, translation를 제공하며 제스처의 좌표 공간도 지정할 수 있습니다. DragGesture.Value, DragGesture

드래그 중에는 선택을 유지하세요. 포인터가 업데이트될 때마다 처음의 선택 작업을 반복하면 포인터가 다른 도형 위를 지날 때 선택 항목이 바뀔 수 있습니다. 마찬가지로 포인터와 중심 좌표 사이의 원래 오프셋을 보존하면 도형의 중심이 갑자기 포인터 아래로 이동하는 현상을 막을 수 있습니다. 이는 앱에서 정해야 하는 설계 규칙이지 Canvas가 자동으로 제공하는 동작이 아닙니다.

예시: 겹침 감지와 이동 차단 비교

설명을 위해 반지름이 각각 20포인트인 원 두 개를 생각해 보겠습니다. 원 A는 (100, 100)에서 시작하고, 움직이지 않는 원 B는 (200, 100)에 있습니다. 사용자는 A의 중심을 (190, 100)로 옮기려 합니다. 이때 두 중심 사이의 거리는 10포인트이고, 두 반지름의 합은 40포인트입니다. 따라서 이 후보 위치에서는 원이 겹칩니다.

겹침 표시 기능이라면 후보 위치를 허용하고 화면 표시를 바꾸세요. 가로 방향으로 드래그하다가 차단하는 경우 처음 맞닿는 위치는 (160, 100)입니다. 이때 두 중심 사이의 거리는 40포인트입니다. 이 계산은 B가 고정되어 있고, 두 원의 크기가 유지되며, A가 수평 경로를 따라 움직인다고 가정합니다.

이제 포인터를 한 번 업데이트해 (260, 100) 위치로 이동한다고 가정해 보겠습니다. 끝점만 검사하면 겹침이 없다고 판단하지만, 이동 경로는 B를 통과합니다. 차단 드래그를 구현하려면 경로를 훑는 교차 검사나 이동 중간 경로를 확인하는 명시적인 다른 방법이 필요합니다. 겹치는 끝점만 거부해서는 “첫 번째 장애물에서 멈추기”를 구현할 수 없습니다.

이 예시는 충돌 반응을 정확히 정해야 하는 가장 큰 이유입니다. 끝점 기하 검사 몇 개만으로 겹침 강조 기능을 완성할 수 있습니다. 장애물을 따라 미끄러지거나, 여러 접촉을 동시에 처리하거나, 주변 항목을 움직이려면 사용자 지정 솔버의 범위가 크게 늘어납니다. Canvas는 여전히 그리기에 사용할 수 있지만, 추가 동작은 앱에서 직접 구현해야 합니다.

SpriteKit은 언제 더 적합한가요?

시뮬레이션으로 충돌 반응을 구현해야 한다면 SpriteKit이 매력적인 선택입니다. 노드에 SKPhysicsBody를 연결하면 중력, 마찰, 충돌을 계산할 수 있으며, 시뮬레이션이 노드의 위치와 방향을 업데이트합니다. 객체를 위한 부피 바디와 경계를 위한 에지 바디를 지원합니다. SKPhysicsBody

첫 프로토타입에서는 서로 떨어진 원 두 개와 사각형 작업 영역 경계를 사용하세요. 원에는 같은 원형 바디를 지정하고, 탁상형 작업 영역이라면 중력을 비활성화하세요. 회전이 상호작용에 필요한지도 정하세요. 마찰, 반발 계수, 감쇠는 기본값에 맡기지 말고 의도에 맞게 설정하세요. 이 속성들은 물리 동작의 서로 다른 측면을 결정합니다. SKPhysicsBody

Apple 문서에 따르면 SpriteKit 장면을 렌더링하는 SwiftUI 뷰인 SpriteView를 사용해 SwiftUI에서 장면을 바로 표시할 수 있습니다. 문서에 명시된 Mac 지원은 macOS 11부터 시작하며, Canvas는 macOS 12부터 지원됩니다. 상호작용 동작이 여전히 주요 기준이지만, 배포 대상도 선택에 포함해야 합니다. SpriteView, Canvas

충돌 반응과 알림을 따로 설정하세요

도형과 경계에 카테고리를 지정한 다음, 두 상호작용 마스크를 명시적으로 설정하세요.

  • categoryBitMask는 바디가 속한 카테고리를 지정합니다.
  • collisionBitMask는 해당 바디에 물리적으로 영향을 줄 수 있는 카테고리를 선택합니다.
  • contactTestBitMask는 선택된 카테고리에 대해 접촉 알림을 요청합니다.

충돌 마스크는 각 바디에서 독립적으로 평가됩니다. 두 동적 도형이 일반적인 상호 반응을 하게 하려면 각 바디가 상대방의 카테고리에 반응하도록 설정하세요. 접촉 보고에는 다른 규칙이 적용됩니다. 한쪽 방향에서 카테고리와 접촉 마스크가 일치하면 알림을 생성할 수 있습니다. 알림을 활성화하는 것만으로는 물리적 차단이 설정되지 않습니다. collisionBitMask, contactTestBitMask

바디가 물리적으로 반응하는데 콜백이 오지 않으면 접촉 마스크와 물리 월드의 접촉 델리게이트를 확인하세요. 도형이 서로 통과하는데 콜백이 온다면 충돌 마스크와 이동 컨트롤러를 확인하세요. 이렇게 점검하면 알림 설정 문제와 실제 이동 문제를 구분할 수 있습니다.

사용자가 드래그하는 동안 위치는 누가 담당하나요?

SpriteKit 드래그에는 명시적인 컨트롤러가 필요합니다. 물리 엔진만으로 포인터를 따라가는 상호작용이 구현되지는 않습니다. 앞서 정한 동작에 따라 직접 배치와 시뮬레이션 기반 이동 중 하나를 선택하세요.

직접 배치에서는 앱 코드가 드래그 중인 노드의 위치를 지정합니다. 포인터로 항목을 정확히 배치해야 할 때 적합하지만, 연속된 위치 사이의 경로가 물리적으로 해결되지는 않습니다. isDynamic를 끄면 바디가 힘과 충격을 무시하게 되지만, 드래그 솔버가 생기는 것은 아닙니다. 이 속성을 일시적으로 바꾼다면 언제 되돌릴지, 놓았을 때 움직임을 유지할지 초기화할지도 정하세요. isDynamic

시뮬레이션 기반 드래그에서는 포인터 위치를 목표로 유지하고 힘이나 스프링 기반 컨트롤러를 사용해 동적 바디를 목표로 유도합니다. 그러면 바디가 움직이는 동안 시뮬레이션이 접촉을 해결할 수 있습니다. 다른 객체가 이동을 막으면 포인터보다 의도적으로 늦거나 덜 움직이게 됩니다. SpriteKit은 힘 적용을 지원하며, SKPhysicsJointSpring은 스프링 동작으로 두 물리 바디를 연결합니다. 이는 앱에 맞춰 조정해야 하는 구성 요소입니다. SKPhysicsBody, SKPhysicsJointSpring

물리가 같은 위치를 독립적으로 제어하기를 기대하면서 포인터 위치를 계속 대입하지 마세요. SpriteKit의 문서화된 프레임 순서에서는 앱 업데이트, 액션, 물리 시뮬레이션, 후속 콜백이 각각 별도의 단계로 실행됩니다. 유용한 위치 제어 규칙은 입력이 드래그 목표를 업데이트하고, 장면 로직이 컨트롤러를 적용하며, 물리가 바디의 최종 위치를 결정하게 하는 것입니다. 프레임 주기 이벤트에 응답하기

접촉 콜백에도 문서화된 제약이 있습니다. Apple에 따르면 콜백은 물리 시뮬레이션 단계에서 실행되며, 이때 물리 월드를 수정할 수 없고 시뮬레이션 중인 바디를 변경하면 동작이 정의되지 않습니다. Apple의 플래그 설정 후 업데이트 패턴에 따라 콜백에서 처리할 작업을 기록하고 다음 업데이트 단계에서 물리 변경을 적용하세요. SKPhysicsContactDelegate

macOS 입력은 프레임워크 경계를 어떻게 넘나들어야 하나요?

작업 영역의 입력 처리를 한 곳에서 담당하도록 선택하세요. Canvas 구현은 상호작용 표면에 SwiftUI DragGesture를 연결할 수 있습니다. 사용자 지정 AppKit 표면은 응답자 이벤트를 처리할 수 있으며, mouseDragged(with:)는 왼쪽 마우스 버튼을 누른 채 이동할 때 호출됩니다. 겹쳐 놓은 SwiftUI 제스처와 AppKit 핸들러가 같은 선택 항목을 각각 움직이게 하지 마세요. DragGesture, mouseDragged(with:)

AppKit과 SpriteKit을 연결할 때는 각 경계에서 좌표를 변환하세요. NSEvent.locationInWindow는 일반적으로 연결된 윈도우의 기준 좌표계로 표현됩니다. 그 지점을 호스팅 뷰 좌표로 변환한 다음, 뷰 좌표를 장면 좌표로 변환하세요. Apple은 후자에 convertPoint(fromView:)을 제공하며, 이를 호출하기 전에 장면을 표시해야 합니다. 노드 위치는 부모의 좌표계로 표현되므로 중첩된 노드는 추가 변환이 필요할 수 있습니다. locationInWindow, convertPoint(fromView:), position

간단한 SwiftUI 호스팅에는 SpriteView를 사용하세요. 사용자 지정 AppKit 뷰가 필요하다면 NSViewRepresentable을 통해 생성, 업데이트, 조정, 해제를 처리할 수 있습니다. 표시되는 뷰의 레이아웃은 SwiftUI가 담당하게 하세요. Apple은 관리 대상 뷰의 프레임이나 바운드를 직접 지정하지 말라고 명시적으로 경고합니다. NSViewRepresentable

SwiftUI가 뷰 콘텐츠를 다시 계산할 때마다 새 장면을 만들지 말고, 장면을 안정적인 소유자에 보관하세요. Canvas에서도 항목 모델을 같은 방식으로 유지하세요. SwiftUI의 모델 문서는 뷰가 다시 생성되어도 관리 상태가 인스턴스를 보존하는 방법을 설명합니다. Observation 방식은 macOS 14 이상이 필요합니다. 장면 명령과 모델 스냅샷을 의도적으로 전달해 두 개의 변경 가능한 사본이 위치를 각각 결정하려고 다투지 않도록 하세요. 앱에서 모델 데이터 관리하기

어느 쪽을 선택할지 결정하기 전에 무엇을 테스트해야 하나요?

시각적 꾸밈을 더하기 전에 작은 합성 작업 영역에서 까다로운 상호작용을 테스트하세요.

  1. 도형 가장자리 가까이를 잡아 초기 오프셋이 유지되는지 확인합니다.
  2. 다른 항목을 향해 천천히 드래그한 다음, 한 번의 큰 포인터 이동으로도 지나가 봅니다.
  3. 주변 도형 두 개가 있는 모서리 쪽으로 드래그합니다.
  4. 다른 항목과 닿은 상태에서 놓고, 예상한 정착 동작을 확인합니다.
  5. 작업 영역 크기를 바꾸고 선택, 경계, 좌표 변환이 올바른지 확인합니다.
  6. 주변 SwiftUI 콘텐츠를 다시 만들고 항목 위치가 유지되는지 확인합니다.
  7. 드래그를 중단하고 선택 항목과 일시적으로 변경된 물리 설정이 정리되는지 확인합니다.

SpriteKit에서는 바디 형상이 의도한 충돌 영역과 일치하는지 확인할 때 SKView.showsPhysics를 켜세요. 빠르게 움직이는 작은 바디에는 usesPreciseCollisionDetection를 검토하세요. Apple 문서에 따르면 이 옵션은 계산 비용이 더 많이 들며, 일반적인 프레임 기반 검사로 놓칠 수 있는 충돌을 감지하는 데 쓰입니다. 이 옵션이 일관된 드래그 컨트롤러를 대신해서는 안 됩니다. SKView, usesPreciseCollisionDetection

배치 규칙이 명확하고 제한되어 있으며 직접 표현하기 쉽다면 Canvas를 선택하세요. 도형 사이의 물리 상호작용이 작업의 일부라면 SpriteKit을 선택하세요. 어느 쪽이든 안정적인 데스크톱 드래그를 위해서는 항목을 하나만 선택하고, 좌표를 명시적으로 변환하며, 각 위치 업데이트를 하나의 구성 요소가 담당해야 합니다.

출처