Link
Notice
HIT해
3D 오브젝트 애니메이션 제어를 위한 Delegate 패턴 구현하기 본문
728x90
개발 배경 및 문제 상황
SwiftUI와 SceneKit을 사용하여 3D 오브젝트(돌)의 애니메이션을 구현했습니다.
기존에는 사용자가 화면에서 돌을 직접 터치했을 때만 애니메이션이 동작했지만, 돌 프로필 조회와 애니메이션 액션이 겹치는 문제가 발생했습니다.
이로 인해 외부 UI 버튼으로도 동일한 애니메이션을 제어할 필요성이 생겼습니다.
문제는 기존 코드에서 3D 오브젝트 애니메이션이 UIKit의 터치 이벤트에만 연결되어 있어 SwiftUI의 버튼 등 다른 UI 요소에서 같은 애니메이션을 트리거하기 어려웠다는 점입니다.
이를 해결하기위해 Delegate 패턴을 활용했습니다.
Delegate 패턴이란?
Delegate 패턴은 객체 간의 통신을 위한 디자인 패턴으로, 한 객체가 특정 작업을 다른 객체에게 위임하는 방식이다.
- 관심사 분리: 객체가 자신의 주요 기능에만 집중하고, 다른 기능은 위임할 수 있다.
- 느슨한 결합: 객체 간의 의존성을 줄이고 코드의 재사용성을 높인다
- 확장성: 코드 변경 없이 새로운 기능을 추가할 수 있다.
해결 과정
1. Static SCNView 구현
첫 번째 단계는 SCNView에 전역적으로 접근할 수 있도록 static 변수로 관리하는 것입니다:
struct DolView: UIViewRepresentable {
// 어디서든 접근 가능한 shared SCNView 인스턴스
private static var sharedSCNView: SCNView?
// 선택된 면과 액세서리를 저장하는 @State 변수들
@Binding var selectedFace: String
@Binding var selectedAccessory: String
func makeUIView(context: Context) -> SCNView {
let scnView = SCNView()
// 생성된 SCNView를 정적 변수에 저장
DolView.sharedSCNView = scnView
// SCNView 설정 코드...
return scnView
}
func updateUIView(_ uiView: SCNView, context: Context) {
// UI 업데이트 코드...
}
}
2. Coordinator 클래스를 활용한 Delegate 패턴 구현
Coordinator 클래스를 확장하여 애니메이션 로직을 처리하는 메서드를 추가했습니다
extension DolView {
class Coordinator: NSObject {
var parent: DolView
init(_ parent: DolView) {
self.parent = parent
}
// 터치 이벤트 핸들러
@objc func handleTapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
let scnView = gestureRecognizer.view as! SCNView
let location = gestureRecognizer.location(in: scnView)
let hitResults = scnView.hitTest(location, options: nil)
if let firstHit = hitResults.first {
let touchedNode = firstHit.node
// 돌의 특정 면이 터치되었는지 확인
if let parentNode = touchedNode.parent, parentNode.name == "\(parent.selectedFace)" {
// 애니메이션 실행
rollDol()
}
}
}
// 애니메이션 로직을 별도 메서드로 분리
func rollDol() {
guard let scnView = DolView.sharedSCNView else { return }
// 선택된 면과 액세서리 노드 찾기
if let faceNode = scnView.scene?.rootNode.childNode(withName: "\(parent.selectedFace)", recursively: true),
let accessoryNode = scnView.scene?.rootNode.childNode(withName: "\(parent.selectedAccessory) reference", recursively: true) {
// 회전 애니메이션
let rotateAction = SCNAction.rotate(by: -2 * .pi, around: SCNVector3(0, 0, 1), duration: 3)
// 이동 애니메이션
let moveAction = SCNAction.moveBy(x: 4, y: 0, z: 0, duration: 3)
// 애니메이션 실행
faceNode.runAction(rotateAction)
accessoryNode.runAction(moveAction)
}
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
}
3. 외부에서 호출 가능한 인터페이스 제공
extension DolView {
// SwiftUI 외부에서 호출할 수 있는 애니메이션 메서드
func rollDol() {
makeCoordinator().rollDol()
}
}
결과물
struct ContentView: View
// DolView에 대한 참조를 저장
@State private var dolViewRef: DolView?
var body: some View {
VStack {
// DolView 생성 및 참조 저장
DolView()
.onViewCreated { view in
self.dolViewRef = view as? DolView
}
// 외부 버튼으로 애니메이션 제어
Button("돌 굴리기") {
dolViewRef?.rollDol()
}
}
}
}
외부에서도 함수 호출만으로 내부 동작을 실행시킬 수 있게 되었습니다.
리팩토링 과정에서 개선을 해볼 필요가 있다 생각되네요.
'Swift > UIKit 개발 노트' 카테고리의 다른 글
RealmSwift 사용해보기 - 걸음수 저장하기 (0) | 2025.02.13 |
---|---|
0x00000001b6b66004 in __abort_with_payload () (0) | 2025.02.13 |
개인프로젝트 진행 계획 (0) | 2025.02.02 |
[Swift/SwiftUI] 높이가 동적으로 변하는 Text 입력란 만들기 (0) | 2024.09.30 |
[Swift/SwiftUI] 줄바꿈 단위 글자단위 <-> 어절단위 (0) | 2024.09.26 |