<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyeonhee_bee.log</title>
        <link>https://velog.io/</link>
        <description>Wellness 잘사는 것에 진심인 웰디입니다. 여러분의 몸과 마음, 통장의 건강을 수호하고싶어요. 느리더라도, 꾸준히</description>
        <lastBuildDate>Mon, 14 Apr 2025 11:12:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyeonhee_bee.log</title>
            <url>https://velog.velcdn.com/images/hyeonhee_bee/profile/473e73b8-bff6-4c46-866a-85914617206c/image.gif</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyeonhee_bee.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyeonhee_bee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2023 레츠스위프트 컨퍼런스]]></title>
            <link>https://velog.io/@hyeonhee_bee/2023-%EB%A0%88%EC%B8%A0%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4</link>
            <guid>https://velog.io/@hyeonhee_bee/2023-%EB%A0%88%EC%B8%A0%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8-%EC%BB%A8%ED%8D%BC%EB%9F%B0%EC%8A%A4</guid>
            <pubDate>Mon, 14 Apr 2025 11:12:02 GMT</pubDate>
            <description><![CDATA[<h3 id="레츠스위프트-컨퍼런스">레츠스위프트 컨퍼런스</h3>
<p>SWIFT TIL카테고리에 올라와 있던 내용을 가져왔습니다. </p>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/fc44427e-601d-4553-aa0f-8094b48150bb/image.png" alt=""></p>
<blockquote>
<p>2023 레츠스위프트 let&#39;s Swift 컨퍼런스에 참여했습니다. 
같은 수강생분들, 러너분들, 단톡방에 계신분들을 만나서 너무 좋았고 행복했어요. 조만간 제대로 후기도 작성해보려고 합니다. 
그전에 데일리로 기록을 남깁니다 :) </p>
</blockquote>
<p>세션은 2~3개정도 오전에 듣고, 오후는 네트워킹시간으로 대부분을 보냈는데 세션도, 네트워킹도 즐겁고 알찬 하루였습니다.
동기부여도 많이 되고, 공부도(물론 오늘공부는 많이 못했지만) 더 열심히 하겠다는 다짐도 하게되었습니다. </p>
<p>너무 ios 초보라 갈까말까 고민했는데, 너무 가길 잘했다고 생각했습니다. 뉴비인데도 반겨주신 많은 분들 너무 감사드립니다. 내년의 레츠스위프트에서는 더 성장해서 저 역시 공유드리고 도움될 수 있는게 많아진다면 좋겠습니다 :) </p>
<h4 id="컨퍼런스-목차">컨퍼런스 목차</h4>
<img src="https://velog.velcdn.com/images/hyeonhee_bee/post/591834b5-dcfc-4d26-b66f-c6d14305ef1f/image.png" width="70%">


<p>자세한 내용은 유튜브에 올라온다고 하니, 저는 가볍게 정리해서 공유해봅니다(기록 겸) 
<strong>(자세한 내용은 추후올라오는 유튜브를 참조해주세요)</strong></p>
<h3 id="들었던-세션">들었던 세션</h3>
<img src="https://velog.velcdn.com/images/hyeonhee_bee/post/efad4674-71b7-44b3-9fcb-dbfa8e92e605/image.png" width="70%">


<h4 id="오픈소스-swift---이승준님">오픈소스 Swift - 이승준님</h4>
<p>애플디벨로퍼아카데미 2기이신줄 몰랐으나, 나중에 알고 인사드릴수있어서 정말 주위분들에게도, 승준님께도 감사한 마음이었다. 일단 중간부터 들어서, 풀로 듣지못해 아쉽긴했지만 중간부터 들은 내용도 너무 좋았고, 개인적으로도 조금이라도 찍먹할 수 있도록 영어공부도 하고, 또 proposal에 지속적인 관심을 가져야겠다고 생각했던 세션, 또 아쉽게 못본 만큼 유튜브에 올라온 영상을 더 챙겨봐야겠다고 생각한 세션, 2기로 활동중이신데 이만큼 아웃풋을 벌써 내시는게 대단하다고 생각했다. </p>
<p><a href="https://www.swift.org/swift-evolution/">https://www.swift.org/swift-evolution/</a></p>
<h4 id="path-of-swift---라이노님">path of Swift - 라이노님</h4>
<p>문법을 공부하는 요즘 듣기 정말 적절했던 세션이었다. 
Swift 라는 언어가 어떻게 기계어(바이너리)가 되는지의 과정을 자세히 설명해주신 세션이었는데, 이러한 내용들을 잘 알고 있으면 나중에 문제해결을 할때 깊이가 달라지겠구나, 문법도 소홀히하지말고 실전도 소홀히 하지말아야겠다는 교훈도 얻었다. </p>
<pre><code>전체과정 : PARSE =&gt; SEMA =&gt; SILGEN =&gt; SilOptimaize =&gt; IR =&gt; LLVM 

&lt;PARSE&gt;

Swift 사용시 =&gt; AST 추상부문트리를 만들어줘야함
AST를 올바르게 만들 수 있는가? Syntax analysis 구문분석
(기계어가 평서어를 이해하기 어려운 이유가 이때문 
=&gt; swift 가 이해하기 쉽게 잘 정리해주는 역할을 함)
* Swift Language Reference공식문서에서 확인가능

매크로 - Swift Syntax 이 AST를 Swift 로 구현한 것 임 (최근나옴)

&lt;SEMA&gt;
AST =&gt; 올바르게 구성되어있는지 검사 : 타입검사, 구조적 검사 등등

아이텐티파이(식별자) 키워드로 구성 
- 키워드 : 컴파일러가 아는 단어
- 식별자 : 개발자들이 추가하고 만든것 
=&gt; 컴파일러가 알지못함 
=&gt; 선언, 정의를 통해 컴파일러가 이해해야함 (이 과정을 Name Lookup이라고 함) 
=&gt; 선언, 정의는 다른 모듈에 있을 수 도 있음

문제점 발생 : 병렬로 돌리는 이유가 없어짐 선형적으로 돌아가는 구조 가 되버림 (어려워짐)
=&gt; 컴파일러는 빠르게 뼈대로 파싱하고, 선언,정의는 Delayed Parsing 으로 필요할때 찾아가는 방식 (딱 필요한 만큼만 할당을 해주는것이 primary 파일을 할당하는 이유이고 이를 토대로 빠르게 파싱한 후 secondary 파일들은 delayed 파싱을 진행한다)


C컴파일러 특징 : 파일자체 컴파일링
C컴파일러 =&gt; 탑다운으로 읽어냄

Swift 컴파일러 =&gt; 탑다운 불가, 꼬여있음 =&gt; 체계화필요 
=&gt; Swift 4.2에서 정보를 채워나가는 방식으로 Request Evaluator 도입됨 =&gt; 성능개선, 기능추가 됨

Swift 컴파일러 특징 : 모듈자체를 컴파일링

Swift 가 읽을 수 있는 모듈 
- 다른 Swift 로 되어있는 모듈(바이너리:읽기편하고 속도빠름 / 컴파일러에 종속적임 
=&gt; 이를 해결을 위해 text형식의 SWift 인터페이스 파일을 사용하기도 함 )
- C C++ (클레이라는 프론트엔드 내장되있음) 모듈 (스위프트 선언으로 바꿔서 가져옴 : 스위프트 컴파일러 내부의 클레이 임포터가 바꿔줌) + 브릿징헤더
- LLVM 타입과 선언을 그대로 사용하는 모듈(표준라이브러리들만 사용가능함) 

&lt;SILGEN&gt;
스위프트 중간언어라는 의미 SIL swift - llvm 중간단계


&lt;SilOptimaize&gt;
최적화 진행  스위프트가 보장해주는 언어의 안정성이 여기서 검사가 됨
=&gt; 최적화 검사완료 : Canonical SIL
+ 추가적인 최적화 옵션이 있다면 성능최적화를 함
=&gt; 역시 Canonical SIL 로 변환

&lt;IR&gt;
IRGEN

&lt;LLVM&gt;
LLVM Backend =&gt; 최종적으로 바이너리로 출력됨</code></pre><h4 id="객체가-함수를-만났을때---권문범님">객체가 함수를 만났을때 - 권문범님</h4>
<p>객체지향과 함수형을 합쳐서 아키텍쳐를 스스로 만들어보신 케이스, 설계능력을 키워볼 수 있고 개발자의 역량강화, 일단 너무 재밌었다는 말씀이 인상깊었다(정말 재밌어보였다) 
언젠가 나도 아키텍쳐를 구현(도전)해보고싶다는 생각도 들었고, 여태까지 배웠던 문법내용(구조체, 열거형, 타입alias , 프로토콜, 확장, 함수, switch 문으로 패턴매칭, 타입선언,  pirvate키워드, 계산속성,set블럭, lazy var, 메서드, super, override, self, self 로 라이프사이클 벗어나도 동작하도록 참조해주기 등등이 진짜 다 모여 하나의 작품을 만드는 느낌이었다. 제대로 다시 보고 이해하고싶은 세션  </p>
<pre><code>&lt;객체지향&gt; 
변경되는 단위를 캡슐화, 내부동작을 몰라도 설계가 쉽도록 
객체지향예시 : 클래스 메시지전달 상속 추상화 캡슐화 다형성 

Swift Concrete Type 
산재된 코드 ⇒ 설계, 재사용성 복잡해짐 ⇒ 추상화, 캡슐화 
내부동작을 몰라도 document 를 통해 재설계하고 만들수있다는 장점을 가짐

예시)
Singleton 패턴 : FileManager, URLSession
Decorator : UIScrollView-Table / UICollectionView
Composite : UIView -addSubView 
그외 등등…

&lt;함수형&gt; 
변경을 최소화해서 선언적으로 구현 / 실행예측이 쉽도록 지원
(사이드 이펙트 최소화, 코드로 바로 직관적 이해 가능하도록 : 
타입변경, 타입일치, 상태변경, 타입상태변경 등등) 

예시)
Pure Function
Functor/Monad (예 : Result) ⇒ Context + Value / map / faltMap / Optional

&lt;객체지향과 함수형&gt; 

PureFunction(인풋+로직 ⇒ 아웃풋) ⇒ 비동기환경에서의 무결성을 보장함 

객체지향 사용하면 좋은 예 : UI공유자원 =&gt; 캡슐화 사용필요 ( MVC 등) 객체지향에 속하게 됨 / Data, File, Network 

함수형 사용하면 좋은 예 : Result / Optional, Combine, Functions, 

&lt;직접만들어보기&gt;

오픈소스를 사용만 하지말고 ( swift 도 오픈소스다 ) 찾아보고 만들어보자 

아키텍쳐를 설계하기 앞서서 원하는 요소들 정리 (발표자님 기준)
- MVVM 아키텍쳐 : 멀티코어, 멀티쓰레드의 데이터 무결성보장가능(함수형을 잘 채용한다는 조건하에) 
- 결과에 따른 유연한 처리지원 
(한번에 결과를 받고, 처리할때만 성공,실패를 알수있도록,
실패의 원인을 바로 찍어줄 수 있도록)
- Flow의 가시성 
- 비동기 처리지원
- API Apple first party 사용

&lt;원하는 요소를 적용하는 과정&gt;
View / Interactor 
⇒ 어떤 Viewcontroller와 관계를 가지는지 선언 
ViewController view bind 및 lifecycle 
⇒ MVVM 의 핵심 (아래와 어떻게 연결할지)
ViewModel Dataflow 가시화 및 관리 
⇒ MVVM의 핵심(위와 어떻게 연결할지)
Data / network 비동기적 데이터 처리 
+) 프로토콜 지향 프로그래밍
구현단계에서 구현가능 , 관계정의도 편리함 
객체간의 인터페이스 선언 데이터 타입의 추상화 
- 라이프사이클의 앞부분 ⇒ 객체형 사용 + 프로토콜
- 라이프사이클의 뒷부분 =&gt; View model 과 데이터처리 사이에 함수형 적용 

+) Result 사용
HIG ⇒ 성공, 실패, 에러처리에 대한 명확성 강조 ⇒ Result 사용 장점 
에러타입 정의 ⇒ Result 의 Failure : 에러타입 으로 전달 


&lt;결과물&gt;
 미리 선언된것들을 한줄의 함수로 정리 ⇒ PURE한 함수가 됨 (외부인풋도 없고 로직은 탑다운으로 문제없이 진행됨) ⇒ 결과에 대한 유연성, flow 가시성, 유연성처리

&lt;직접만든 아키텍쳐 사용해보기&gt;
협업시 에러띄워주는게 가장효과적임
(이렇게 미리 타입선언 등을 다 해놓으면 자동으로 에러띄우고, 
자동완성을 해주니까 매우 효과적임)
</code></pre><h4 id="당근ios">당근ios</h4>
<p>채용준비 하시는 분들이 제일 관심이 많으셨던것 같은 당근 ios 세션 
기술적인 내용은 유튜브에서 더 자세히 설명을 들으실 수 있을것같고(절반정도 들었기 때문..!) </p>
<p>가장 궁금해하실것같은 당근ios에서 고민하고 있는 문제들을 공유합니다. </p>
<p>*<em>고민하고 있는 문제들 (채용과 연계) *</em></p>
<pre><code>&lt;모바일팀&gt;

- 계정, 알림, 데이터, 실험, 보안 등 서비스의 기반이 되는 코어기능 재설계
- 자동화, Horizontal Component개발을 통한 엔지니어 생산성 증대
- 도메인개발을 위한 안정적인 설계방향가이드

&lt;채팅&gt;
- 글로벌서비스(국내유저와 글로벌유저의 채팅경험 다름)
- 그룹채팅기능
- 동네생활 동네알바등 다양한 서비스 연동
- 다양한기능 가능한 유연한 구조 설계</code></pre><p>제 기준으로 (정말 초보자기준) 정리한 내용들이라 실제 연사님의 의도와는 차이가 있을 수 있으니 
유튜브로 진행되는 세션에서 더 정확한 정보를 얻어가는 시간 되시면 좋을것같습니다. 
행사 제작하시느라 고생하신 오거나이저 분들, 스폰서분들, 연사분들, 또 참여하신 모든 참가자분들 고생많으셨습니다! </p>
<p>모두 즐거운 swift 되세요 :) </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드조각] [3] 업로드한 오브젝트에 제스쳐 적용하기(오브젝트 이동, 스케일링, 히든, 애니메이션) ]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-3-%EC%97%85%EB%A1%9C%EB%93%9C%ED%95%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%9C%EC%8A%A4%EC%B3%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EB%8F%99-%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81-%ED%9E%88%EB%93%A0-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-3-%EC%97%85%EB%A1%9C%EB%93%9C%ED%95%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%9C%EC%8A%A4%EC%B3%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EB%8F%99-%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81-%ED%9E%88%EB%93%A0-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98</guid>
            <pubDate>Mon, 14 Apr 2025 11:08:53 GMT</pubDate>
            <description><![CDATA[<h3 id="제스쳐를-인식하여-오브젝트에-적용하는-코드조각">제스쳐를 인식하여 오브젝트에 적용하는 코드조각</h3>
<h4 id="제스쳐-recognizer">제스쳐 recognizer</h4>
<pre><code class="language-swift"> func addGestureRecognizers() {        
 // 탭 제스처 추가        
 let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(gesture:)))        
 tapGestureRecognizer.delegate = self        
 sceneView.addGestureRecognizer(tapGestureRecognizer)        
 // 팬 제스처 추가 (드래그)        
 let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))        
 sceneView.addGestureRecognizer(panGestureRecognizer)        
 // 핀치 제스처 추가        
 let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))        
 sceneView.addGestureRecognizer(pinchGestureRecognizer)        
 // 롱프레스 제스처 추가        
 let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))        
 // 최소  2초동안은 프레싱되어야 제스쳐를 인식합니다        
 longPressGestureRecognizer.minimumPressDuration = 2        
 // 2초 안에 들어오는 제스쳐는 삭제합니다(버리기)        
 longPressGestureRecognizer.delaysTouchesBegan = true        
 sceneView.addGestureRecognizer(longPressGestureRecognizer)    
 }</code></pre>
<h4 id="제스쳐-함수-오브젝트를-이동">제스쳐 함수 (오브젝트를 이동)</h4>
<pre><code class="language-swift"> @objc func handlePan(_ gesture: UIPanGestureRecognizer) {
    //  gesture.location(in: sceneView)는 제스처 이벤트가 발생한 터치 위치를 sceneView 좌표계에서 반환
    let location = gesture.location(in: sceneView)
    switch gesture.state {
    case .began:
        // 제스처의 상태를 확인합니다. 여기서는 제스처가 시작될 때(.began)만 코드를 실행합니다.
        let hitResults = sceneView.hitTest(location, options: nil)
        // sceneView.hitTest(location, options: nil)는 터치 위치(location)에서 히트 테스트를 수행하여 해당 위치에 있는 노드들을 반환(히트 테스트는 터치된 지점에 어떤 노드들이 있는지 확인하는 과정임)
        if let hitResult = hitResults.first {                
        selectedNode = hitResult.node
            //히트 테스트 결과에서 첫 번째 노드를 selectedNode로 선택합니다. hitResults.first는 히트된 노드들 중 첫 번째 노드를 반환합니다.
            while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {                    
            selectedNode = parent
        }
            /* 
             - 선택된 노드의 최상위 부모 노드를 찾기 위해 `while` 루프를 사용합니다.                - `selectedNode?.parent`가 `nil`이 아니고, `selectedNode`의 부모가 `sceneView.scene.rootNode`가 아닐 때까지 루프를 계속 실행합니다.                - `selectedNode`를 계속 부모 노드로 갱신하여 최상위 노드에 도달할 때까지 반복합니다.                
              */
            originalNodePosition = selectedNode ? .position
        }
    case.changed: if
        let selectedNode = selectedNode,
        let originalNodePosition = originalNodePosition {
        // transition은 제스처 인식기에서 현재 팬 동작의 이동 거리를 가져옵니다. 이 거리는 사용자가 화면에서 손가락을 얼마나 이동했는지를 나타냅니다.
        let translation = gesture.translation(in: sceneView)                
        let newPosition = SCNVector3(                    
        x: originalNodePosition.x + Float(translation.x * 0.05),                    y: originalNodePosition.y + Float(translation.y * -0.05),                    z: originalNodePosition.z + Float(translation.y * -0.05)                
        )
        /*    
        - `translation` 값을 이용하여 `newPosition`을 계산합니다.                 - `originalNodePosition.x`에 `translation.x`을 더하여 노드의 새로운 x 위치를 계산합니다. `translation.x`에 0.001을 곱한 것은 화면상의 이동 거리를 3D 공간상의 이동 거리로 변환하기 위함입니다.                 - `originalNodePosition.y`는 그대로 유지됩니다. 이는 노드의 높이(y 축)를 변경하지 않음을 의미합니다.                 - `originalNodePosition.z`에 `translation.y`을 더하여 노드의 새로운 z 위치를 계산합니다. 마찬가지로 `translation.y`에 0.001을 곱한 것은 화면상의 이동 거리를 3D 공간상의 이동 거리로 변환하기 위함입니다.                 
        */
        selectedNode.position = newPosition
        // addAnimation(node: selectedNode)
        // addMoveUpDownAnimation(node: selectedNode)
    }
    case .ended, .cancelled:
        selectedNode = nil
        originalNodePosition = nil
    default:
        break
    }
}</code></pre>
<h4 id="제스쳐-함수-오브젝트-스케일링">제스쳐 함수 (오브젝트 스케일링)</h4>
<pre><code class="language-swift">@objc func handlePinch(_ gesture: UIPinchGestureRecognizer) {        
let location = gesture.location(in: (gesture.view as! ARSCNView))        
switch gesture.state {        
    case .began:            
    //  let hitResults = sceneView.hitTest(location, options: nil)            
        let hitTest = (gesture.view as! ARSCNView).hitTest(location)            
        if let hitTest = hitTest.first {                
        // 최상위 노드로 이동                
        selectedNode = hitTest.node
                while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {  
                selectedNode = parent
             }                
                originalNodePosition = selectedNode?.position
            }        
            case .changed:            
                if let selectedNode = selectedNode, let originalScale = originalScale {     
                let scale = Float(gesture.scale)                
                selectedNode.scale = SCNVector3(x: originalScale.x * scale, y: originalScale.y * scale, z: originalScale.z * scale)            
                }        
                case .ended, .cancelled:            
                if let selectedNode = selectedNode 
                {                
                originalScale = selectedNode.scale
                   }        
            default:            
                break        
 }    
}</code></pre>
<h4 id="제스쳐-함수-오브젝트-hidden">제스쳐 함수 (오브젝트 hidden)</h4>
<pre><code class="language-swift">    @objc func handleTap(gesture: UITapGestureRecognizer) {        
    let location = gesture.location(in: sceneView)            
    let hitResults = sceneView.hitTest(location, options: nil)            
    if let hitResult = hitResults.first {                
    selectedNode = hitResult.node
                while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {                    
                selectedNode = parent
                }                
                originalNodePosition = selectedNode?.position
                originalScale = selectedNode?.scale
                if let selectedNode = selectedNode {                    
                for i in 1...64 {                        
                let nodeName = String(format: &quot;Feu%02d&quot;, i) 
                if let fireNode = selectedNode.childNode(withName: nodeName, recursively: true) {                            
                if fireNode.isHidden {                                
                fireNode.isHidden = false                                
                playSound(currentModelName)                            
                }                            
                else 
                {                                
                fireNode.isHidden = true                                
                pauseSound(currentModelName)                            
                }                        
               }                    
             }                    
             let candleNodeName = &quot;Cone_3&quot;                    
             if let candleNode = selectedNode.childNode(withName: candleNodeName, recursively: true) {                        
             if let candleInnerNode = candleNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
             if candleInnerNode.isHidden 
             {                                
             candleInnerNode.isHidden = false                                
             playSound(currentModelName)                           
             }                            
            else {                                
            candleInnerNode.isHidden = true                                
            pauseSound(currentModelName)                            
            }                        
           }                    
          }                    
          let starOrbNodeName = &quot;inner_0&quot;                    
          if let starOrbInnerNode = selectedNode.childNode(withName: starOrbNodeName, recursively: true) {                        
          if let starOrbCenterNode = starOrbInnerNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
          if let starOrbNode = starOrbCenterNode.childNode(withName: &quot;Object_0&quot;, recursively: true) {                                
          if starOrbNode.isHidden {                                    
          starOrbNode.isHidden = false                                    
          playSound(currentModelName)                                
          }                                
          else {                                    
          starOrbNode.isHidden = true                                    
          pauseSound(currentModelName)                                
          }                            
         }                        
        }                    
       }               
      }            
       }    
      }</code></pre>
<h4 id="제스쳐-함수-오브젝트-애니메이션">제스쳐 함수 (오브젝트 애니메이션)</h4>
<pre><code class="language-swift">    @objc func handleLongPress(_ gesture: UILongPressGestureRecognizer) {        
    let location = gesture.location(in: sceneView)        
    switch gesture.state {        
    case .began:            
    let hitResults = sceneView.hitTest(location, options: nil)            
    if let hitResult = hitResults.first {                
    selectedNode = hitResult.node
                while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {                    
                selectedNode = parent
        }                
                originalNodePosition = selectedNode?.position
                originalScale = selectedNode?.scale
                if let selectedNode = selectedNode {                    
                for i in 1...64 {                        
                let nodeName = String(format: &quot;Feu%02d&quot;, i)            
                if let fireNode = selectedNode.childNode(withName: nodeName, recursively: true) {                            
                let scaleZAction = SCNAction.scaleZ(to: 5, duration: 5.0)                            adjustPivot(node: fireNode)                            
                fireNode.runAction(scaleZAction)                        
                }                    
                }                    
                let candleNodeName = &quot;Cone_3&quot;                    
                if let candleNode = selectedNode.childNode(withName: candleNodeName, recursively: true) {
                if let candleInnerNode = candleNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
                let scaleYAction = SCNAction.scaleY(to: 5, duration: 5.0)                            adjustPivot(node: candleInnerNode)                            candleInnerNode.runAction(scaleYAction)                            
                if candleInnerNode.action(forKey: &quot;scaleY&quot;) != nil {             
                let scaleYAction = SCNAction.scaleY(to: 1, duration: 5.0)
                //adjustPivot(node: candleInnerNode)                                candleInnerNode.runAction(scaleYAction)                            
                }                        
                }                    
                }                    
                let starOrbNodeName = &quot;inner_0&quot;                    
                if let starOrbInnerNode = selectedNode.childNode(withName: starOrbNodeName, recursively: true) {                        
                if let starOrbCenterNode = starOrbInnerNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
                if let starOrbNode = starOrbCenterNode.childNode(withName: &quot;Object_0&quot;, recursively: true) {                                
                let scaleAction = SCNAction.scale(to: 5.0, duration: 2.5)                        starOrbNode.runAction(scaleAction)       
                if starOrbNode.action(forKey: &quot;scale&quot;) != nil { 
                let scaleAction = SCNAction.scale(to: 1.0, duration: 2.5)  
                starOrbNode.runAction(scaleAction)  
                }                            
                }                        
                        }                    
                    }                
                }            
            }        
            case .ended, .cancelled:            
            let hitResults = sceneView.hitTest(location, options: nil)            
            if let hitResult = hitResults.first {                
            selectedNode = hitResult.node
                while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {                    
                selectedNode = parent
             }                
                originalNodePosition = selectedNode?.position
                originalScale = selectedNode?.scale
                if let selectedNode = selectedNode {                    
                for i in 1...64 {                        
                let nodeName = String(format: &quot;Feu%02d&quot;, i)     
                if let fireNode = selectedNode.childNode(withName: nodeName, recursively: true) {                           
                if fireNode.action(forKey: &quot;scaleZ&quot;) != nil {  
                let scaleZAction = SCNAction.scaleZ(to: 1, duration: 5.0)                        adjustPivot(node: fireNode)                                
                fireNode.runAction(scaleZAction)                            
                }                            
                fireNode.removeAllActions()                        
              }                    
            }                    
            let candleNodeName = &quot;Cone_3&quot;                    
            if let candleNode = selectedNode.childNode(withName: candleNodeName, recursively: true) {                        
            if let candleInnerNode = candleNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
            candleInnerNode.removeAllActions()                        
            }                    
          }                    
          let starOrbNodeName = &quot;inner_0&quot;                    
          if let starOrbInnerNode = selectedNode.childNode(withName: starOrbNodeName, recursively: true) {                        
          if let starOrbCenterNode = starOrbInnerNode.childNode(withName: &quot;Object_4&quot;, recursively: true) {                            
          if let starOrbNode = starOrbCenterNode.childNode(withName: &quot;Object_0&quot;, recursively: true) {                                
          starOrbNode.removeAllActions()                            
          }                        
        }                    
      }                
    }            
  }            
  selectedNode = nil            
  originalNodePosition = nil            
  originalScale = nil            
  startY = nil            
  originalZScale = nil        
  default:            
      break        
   }    
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드조각] [2] 오브젝트를 AR환경에 올리기]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-2-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8%EB%A5%BC-AR%ED%99%98%EA%B2%BD%EC%97%90-%EC%98%AC%EB%A6%AC%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-2-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8%EB%A5%BC-AR%ED%99%98%EA%B2%BD%EC%97%90-%EC%98%AC%EB%A6%AC%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 11:07:42 GMT</pubDate>
            <description><![CDATA[<h3 id="오브젝트를-올리는-코드조각">오브젝트를 올리는 코드조각</h3>
<pre><code class="language-jsx">override func viewDidLoad() {
    super.viewDidLoad()
    // Set the view&#39;s delegate    sceneView.delegate = self
    // 씬 생성    let scene = SCNScene()
    // 씬 뷰에 SCNScene 인스턴스를 올림    sceneView.scene = scene
   // 인벤토리 버튼 추가    setupInventoryButton()
    // 제스처 인식기 추가    addGestureRecognizers()
}</code></pre>
<pre><code class="language-swift"> func loadUSDZModel(named modelName: String) {        
 // sceneView의 씬에서 루트 노드의 모든 자식 노드를 제거        
 // forEach 문으로 루트 노드의 모든 자식노드를 돌면서 씬의 루트 노드에 있는 모든 자식 노드를 제거함 
        sceneView.scene.rootNode.childNodes.forEach { 
        $0.removeFromParentNode() 
  }        
        guard let url = Bundle.main.url(forResource: &quot;art.scnassets/\(modelName)&quot;, withExtension: &quot;usdz&quot;), let node = SCNReferenceNode(url: url) else { 
            print(&quot;USDZ 파일을 찾을 수 없습니다: \(modelName)&quot;)            
            return       
  }        
 // 일부 객체가 검게 보이는 현상이 있으므로 다시 조명추가해줌        
 // 씬에서 오브젝트를 갈아낄때 노드가 삭제되면서 조명(라이팅)이 제거된 것으로 보임        
 addLighting()        
 currentModelName = modelName
 // 소리를 재생합니다        
 playSound(modelName)        
 // 노드를 올립니다        
 node.load()        
 // Animated_fire 모델의 특성으로 z축으로 떨어져서 보내게 했습니다        
 node.position = SCNVector3(x: 0, y: 0, z: -15)        
 // 원하는 스케일로 조정        
 node.scale = SCNVector3(x: 0.15, y: 0.15, z: 0.15)        
 // 씬의 루트노드에 자식노드를 추가합니다        
 sceneView.scene.rootNode.addChildNode(node)        
 // 핀치 제스쳐 관련으로 selectedNode 속성을 사용했었으나 지금은 쓰지않음(일단 넣어뒀음 - 추후 삭제예정)        
 // selectedNode = node        
 // addAnimation(node: node)        
 originalScale = node.scale

 }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코드조각] [1] AR 환경설정하기 ]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-1-AR-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/%EC%BD%94%EB%93%9C%EC%A1%B0%EA%B0%81-1-AR-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 11:06:56 GMT</pubDate>
            <description><![CDATA[<h3 id="👩💻-ar-환경을-설정하는-코드조각">👩‍💻 AR 환경을 설정하는 코드조각</h3>
<pre><code class="language-swift">class ViewController: UIViewController, ARSCNViewDelegate, UIGestureRecognizerDelegate  {    
// ARKit과 SceneKit을 결합한 뷰로, ARKit을 사용하여 실제 환경을 추적하고 SceneKit을 사용하여 3D 콘텐츠를 렌더링하는 역할    
@IBOutlet var sceneView: ARSCNView!    
// ARSession 설정 함수    
func configureARSession() {    
//ARWorldTrackingConfiguration을 사용하여 디바이스의 위치와 방향을 추적하고, AR 콘텐츠를 실제 세계와 맞추는 설정을 함        
let configuration = ARWorldTrackingConfiguration()          
// 사람을 인식해서 사람 사이의 거리를 계산하여 객체를 위치시키는 기능          
// 장면에서 사람의 깊이와 관계없이 앱의 가상 콘텐츠에 사람이 겹쳐야 함을 나타내려면 personSegmentation 프레임 시맨틱을 사용합니다.          
// 객체를 강조하기 위해 해당 코드 생략가능(객체가 최상단에 오는것처럼 보입니다) 
configuration.frameSemantics.insert(.personSegmentation)        
// - ARKit가 디바이스의 위치와 방향을 추적하는 데 필요한 정보를 `detectionObjects` (ARKit이 감지할 객체의 목록) 으로 설정        
//- &quot;AR Resources&quot;라는 이름의 그룹에서 객체를 검색하되, nil로 설정하여 기본 번들을 사용하였음 , referenceObject가 nil을 반환할경우(목록이 nil일때) 빈배열 [] 사용       
configuration.detectionObjects = ARReferenceObject.referenceObjects(inGroupNamed: &quot;AR Resources&quot;, bundle: nil) ?? []        
// - 세션은 ARKit이 현재 디바이스의 카메라 피드와 센서 데이터를 처리하고, 이를 기반으로 가상 콘텐츠를 렌더링하는 데 사용, 주어진 구성과 옵션을 사용하여 AR 세션을 시작함.        
// - .removeExistingAnchors로 기존 앵커를 제거하는 옵션 제공,새로운 세션이 시작될 때 이전 세션의 앵커를 모두 제거하여 초기 상태로 시작하게 하는 옵션        
// .resetTracking : 추적 상태를 리셋하는 옵션 설정, 새로운 세션이 시작될 때 디바이스의 추적 상태를 초기화하여  이를 통해 세션이 시작될 때 정확한 위치와 방향을 다시 설정할 수 있게함.        
sceneView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])    
}    
... }</code></pre>
<p>ARKit 으로 세션을 설정하고 시작하는 과정 : <code>ARWorldTrackingConfiguration</code>을 사용하여 객체 감지를 설정하고 <code>ARSCNView</code>의 세션을 시작하는 코드조각 입니다.</p>
<p>추가 설정으로 객체를 감지하고, 디바이스의 위치와 방향을 추적하고, 감지된 객체를 기반으로 가상 콘텐츠를 렌더링하게 됩니다. 새로운 세션이 시작된다면 기존의 앵커와 추적 정보를 초기화하는 기능을 추가했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번외]SceneKit - 노드 제어 이론]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EB%B2%88%EC%99%B8-SceneKit-%EB%85%B8%EB%93%9C-%EC%A0%9C%EC%96%B4</link>
            <guid>https://velog.io/@hyeonhee_bee/%EB%B2%88%EC%99%B8-SceneKit-%EB%85%B8%EB%93%9C-%EC%A0%9C%EC%96%B4</guid>
            <pubDate>Mon, 14 Apr 2025 11:04:07 GMT</pubDate>
            <description><![CDATA[<p><strong>_
*코드는 직접 코드에디터에 붙여넣고 주석을 확인하시는 편이 가독성에 좋습니다_</strong>
공식문서의 설명을 번역했습니다. 번역한 설명을 토대로 주석을 한줄마다 달았습니다. </p>
<h3 id="노드물체-씬에-올리기">노드(물체) 씬에 올리기</h3>
<pre><code class="language-jsx">override func viewDidLoad() {
    super.viewDidLoad()
    // Set the view&#39;s delegate    sceneView.delegate = self
    // 씬 생성    let scene = SCNScene()
    // 씬 뷰에 SCNScene 인스턴스를 올림    sceneView.scene = scene
   // 인벤토리 버튼 추가    setupInventoryButton()
    // 제스처 인식기 추가    addGestureRecognizers()
}</code></pre>
<pre><code class="language-swift"> func loadUSDZModel(named modelName: String) {        
 // sceneView의 씬에서 루트 노드의 모든 자식 노드를 제거        
 // forEach 문으로 루트 노드의 모든 자식노드를 돌면서 씬의 루트 노드에 있는 모든 자식 노드를 제거함        
 sceneView.scene.rootNode.childNodes.forEach { $0.removeFromParentNode() 
 }        
 guard let url = Bundle.main.url(forResource: &quot;art.scnassets/\(modelName)&quot;, withExtension: &quot;usdz&quot;),let node = SCNReferenceNode(url: url) else {            
 print(&quot;USDZ 파일을 찾을 수 없습니다: \(modelName)&quot;)            
 return        
 }        
 // 일부 객체가 검게 보이는 현상이 있으므로 다시 조명추가해줌       
 // 씬에서 오브젝트를 갈아낄때 노드가 삭제되면서 조명(라이팅)이 제거된 것으로 보임        
 addLighting()        
 currentModelName = modelName
 // 소리를 재생합니다        
 playSound(modelName)        
 // 노드를 올립니다        
 node.load()        
 // Animated_fire 모델의 특성으로 z축으로 떨어져서 보내게 했습니다        
 node.position = SCNVector3(x: 0, y: 0, z: -15)        
 // 원하는 스케일로 조정        
 node.scale = SCNVector3(x: 0.15, y: 0.15, z: 0.15)        
 // 씬의 루트노드에 자식노드를 추가합니다        
 sceneView.scene.rootNode.addChildNode(node)        
 // 핀치 제스쳐 관련으로 selectedNode 속성을 사용했었으나 지금은 쓰지않음(일단 넣어뒀음 - 추후 삭제예정)        
 // selectedNode = node        
 // addAnimation(node: node)        
 originalScale = node.scale
 }</code></pre>
<h3 id="노드물체를-씬에-올렸다면-제스쳐를-적용해봅시다">노드(물체)를 씬에 올렸다면 제스쳐를 적용해봅시다</h3>
<p>해당 프로젝트에서는 기본적으로 UIkit 를 사용하므로 UIkit 에서 어떻게 핸드제스쳐를 이용하는지와 오브젝트를 제스쳐로 인식하는 방법을 설명합니다.</p>
<p>제스쳐를 인식하기 위한 제스쳐 recognizer을 viewDidLoad 부분에 올린 후, 해당 제스쳐가 인식되었을때 구동되는 함수를 따로 작성합니다</p>
<h4 id="제스쳐-recognizer-로-제스쳐를-인식하게-합니다">제스쳐 recognizer 로 제스쳐를 인식하게 합니다</h4>
<p>예) 하단의 코드에서는 함수 handleTap / handlePan / handlePinch /handleLongPress 등이 그 역할을 수행하고 있습니다</p>
<pre><code class="language-swift"> func addGestureRecognizers() {        // 탭 제스처 추가        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(gesture:)))        tapGestureRecognizer.delegate = self        sceneView.addGestureRecognizer(tapGestureRecognizer)        // 팬 제스처 추가 (드래그)        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))        sceneView.addGestureRecognizer(panGestureRecognizer)        // 핀치 제스처 추가        let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))        sceneView.addGestureRecognizer(pinchGestureRecognizer)        // 롱프레스 제스처 추가        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))        // 최소  2초동안은 프레싱되어야 제스쳐를 인식합니다        longPressGestureRecognizer.minimumPressDuration = 2        // 2초 안에 들어오는 제스쳐는 삭제합니다(버리기)        longPressGestureRecognizer.delaysTouchesBegan = true        sceneView.addGestureRecognizer(longPressGestureRecognizer)    }</code></pre>
<ul>
<li><p>롱프레스 제스쳐 셋팅 관련 공식문서 (참고용)</p>
<h1 id="uilongpressgesturerecognizer"><strong>UILongPressGestureRecognizer</strong></h1>
<p>  <strong>A continuous gesture recognizer that interprets long-press gestures.</strong></p>
<p>  iOS 3.2+iPadOS 3.2+Mac Catalyst 13.1+tvOS 9.0+visionOS 1.0+</p>
<p>  <code>@MainActor   class UILongPressGestureRecognizer : [UIGestureRecognizer](https://developer.apple.com/documentation/uikit/uigesturerecognizer)</code></p>
<h3 id="개요">개요</h3>
<p>  <code>UILongPressGestureRecognizer</code> is a concrete subclass of <a href="https://developer.apple.com/documentation/uikit/uigesturerecognizer"><code>UIGestureRecognizer</code></a>. UILongPressGestureRecognizer는 UIGestureRecognizer의 구체적인 서브클래스입니다.</p>
<p>  The user must press one or more fingers on a view and hold them there for a minimum period of time before the action triggers. While down, the userʼs fingers canʼt move more than a specified distance or the gesture fails. 사용자는 동작이 트리거되기 전에 뷰를 하나 이상의 손가락으로 누르고 최소 시간 동안 유지해야 합니다. 누르고 있는 동안 사용자의 손가락이 지정된 거리 이상 움직이지 않으면 제스처가 실패합니다.</p>
<p>  A long-press gesture is continuous. The gesture begins (<a href="https://developer.apple.com/documentation/uikit/uigesturerecognizer/state/began"><code>UIGestureRecognizer.State.began</code></a>) when the user presses the number of allowable fingers (<a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616425-numberoftouchesrequired"><code>numberOfTouchesRequired</code></a>) for the specified period (<a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616423-minimumpressduration"><code>minimumPressDuration</code></a>) and the touches don’t move beyond the allowable range of movement (<a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616427-allowablemovement"><code>allowableMovement</code></a>). The gesture recognizer transitions to the Change state whenever a finger moves, and it ends (<a href="https://developer.apple.com/documentation/uikit/uigesturerecognizer/state/ended"><code>UIGestureRecognizer.State.ended</code></a>) when the user lifts any of the fingers.</p>
<p>  길게 누르는 제스처가 계속됩니다. 제스처는 사용자가 지정된 기간(minimumPressDuration) 동안 허용되는 손가락 수(numberOfTouchesRequired)를 누르고 터치가 허용된 이동 범위(allowableMovement)를 벗어나지 않으면 시작됩니다(UIGestureRecognizer.State.began). 제스처 인식기는 손가락이 움직일 때마다 변경 상태로 전환되며, 사용자가 손가락을 떼면 종료(UIGestureRecognizer.State.ended)됩니다.</p>
<ul>
<li><p><a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer#1658285">*Configuring the gesture recognizer</a>* 제스처 인식기 구성하기</p>
<p><a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616423-minimumpressduration"><code>var minimumPressDuration: TimeInterval</code></a></p>
<p>The minimum time that the user must press on the view for the gesture to be recognized.
제스처를 인식하기 위해 사용자가 뷰를 눌러야 하는 최소 시간입니다.</p>
<p><a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616425-numberoftouchesrequired"><code>var numberOfTouchesRequired: Int</code></a></p>
<p>The number of fingers that must touch the view for gesture recognition.
제스처 인식을 위해 뷰를 터치해야 하는 손가락의 수입니다.</p>
<p><a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616424-numberoftapsrequired"><code>var numberOfTapsRequired: Int</code></a></p>
<p>The number of taps on the view necessary for gesture recognition.
제스처 인식에 필요한 뷰의 탭 횟수입니다.</p>
<p><a href="https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616427-allowablemovement"><code>var allowableMovement: CGFloat</code></a></p>
<p>The maximum movement of the fingers on the view before the gesture fails.
제스처가 실패하기 전 뷰에서 손가락의 최대 움직임입니다.</p>
<pre><code class="language-swift">let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))lpgr.minimumPressDuration = 0.5lpgr.delaysTouchesBegan = truelpgr.delegate = selfself.colVw.addGestureRecognizer(lpgr)//MARK: - UILongPressGestureRecognizer Action -    @objc func handleLongPress(gestureReconizer: UILongPressGestureRecognizer) {        if gestureReconizer.state != UIGestureRecognizer.State.ended {            //When lognpress is start or running        }        else {            //When lognpress is finish        }    }</code></pre>
<p>핸들링을 더 잘하기위해 state를 받아서 began, changed, ended로 분기할 수 있다</p>
<p>[추가개념]</p>
</li>
<li><p><em>delaysTouchesBegan*</em></p>
<p>제스처 인식기가 시작 단계의 터치를 뷰로 전송하는 것을 지연시킬지 여부를 결정하는 부울 값입니다.</p>
<p><code>var delaysTouchesBegan: [Bool](https://developer.apple.com/documentation/swift/bool) { get set }</code>
이 속성의 값이 false(기본값)이면 뷰는 제스처 인식기와 병렬로 <a href="https://developer.apple.com/documentation/uikit/uitouch/phase/began"><code>UITouch.Phase.began</code></a> 와 <a href="https://developer.apple.com/documentation/uikit/uitouch/phase/moved"><code>UITouch.Phase.moved</code></a> 터치 이벤트를 분석합니다. 이 속성의 값이 참이면 창은 <code>UITouch.Phase.began</code>단계의 터치 개체를 뷰로 전달하는 것을 일시 중단합니다. 이후 제스처 인식기가 제스처를 인식하면 이러한 터치 개체는 버려집니다. 그러나 제스처 인식기가 제스처를 인식하지 못하면 창은 터치의 현재 위치를 알려주는 후속 <a href="https://developer.apple.com/documentation/uikit/uiresponder/1621107-touchesmoved"><code>touchesMoved(_:with:)</code></a>  <em>메시지와 함께 이러한 개체를 뷰에 전달합니다(</em> <a href="https://developer.apple.com/documentation/uikit/uiresponder/1621107-touchesmoved"><code>touchesMoved(_:with:)</code></a>  메시지도 포함될 수 있음). 이 속성을 true로 설정하면 뷰에서 이 제스처의 일부로 인식될 수 있는 <code>UITouch.Phase.began</code>단계의 터치를 처리하지 않습니다.</p>
</li>
</ul>
</li>
</ul>
<h4 id="해당-제스쳐를-인식했나요-그럼-오브젝트에-변화를-줘-봅시다">해당 제스쳐를 인식했나요? 그럼 오브젝트에 변화를 줘 봅시다</h4>
<p>기본적으로 SCNNode 에서 보듯 노드를 찾아서 오브젝트를 변경시킬 수 있습니다. 오브젝트의 위치, 오브젝트의 크기, 오브젝트내에서도 노드가 분리되어 있다면 특정노드(특정 콘텐츠)를 찾아 별개로 애니메이션을 적용할 수 있습니다.</p>
<p>예) 하단의 코드에서는 pan 제스쳐(꾹 누른 상태로 움직이는 제스쳐)에 따라 최상단 노드(최상단 노드가 아니면 일부 노드만/ 즉 오브젝트의 일부분만 옮겨질 수 있습니다)를 찾아서 제스쳐의 위치에 따라 오브젝트의 위치도 변경되는 코드가 구현되어 있습니다.</p>
<p><code>handlePan</code> 메서드는 <code>hitTest</code> 결과로 반환된 노드의 최상위 부모 노드를 선택하여 이동합니다. 이를 통해 <code>USDZ</code> 모델의 전체 구조를 함께 이동할 수 있습니다.</p>
<p>좌표수정 : 오브젝트 이동 대충가능해짐</p>
<pre><code class="language-swift"> @objc func handlePan(_ gesture: UIPanGestureRecognizer) {      //  gesture.location(in: sceneView)는 제스처 이벤트가 발생한 터치 위치를 sceneView 좌표계에서 반환        let location = gesture.location(in: sceneView)        switch gesture.state {        case .began:        // 제스처의 상태를 확인합니다. 여기서는 제스처가 시작될 때(.began)만 코드를 실행합니다.            let hitResults = sceneView.hitTest(location, options: nil)        // sceneView.hitTest(location, options: nil)는 터치 위치(location)에서 히트 테스트를 수행하여 해당 위치에 있는 노드들을 반환(히트 테스트는 터치된 지점에 어떤 노드들이 있는지 확인하는 과정임)            if let hitResult = hitResults.first {                selectedNode = hitResult.node
                //히트 테스트 결과에서 첫 번째 노드를 selectedNode로 선택합니다. hitResults.first는 히트된 노드들 중 첫 번째 노드를 반환합니다.                while let parent = selectedNode?.parent, parent !== sceneView.scene.rootNode {                    selectedNode = parent
                }                /*                - 선택된 노드의 최상위 부모 노드를 찾기 위해 `while` 루프를 사용합니다.                - `selectedNode?.parent`가 `nil`이 아니고, `selectedNode`의 부모가 `sceneView.scene.rootNode`가 아닐 때까지 루프를 계속 실행합니다.                - `selectedNode`를 계속 부모 노드로 갱신하여 최상위 노드에 도달할 때까지 반복합니다.                 */                originalNodePosition = selectedNode?.position
            }        case .changed:            if let selectedNode = selectedNode, let originalNodePosition = originalNodePosition  {                // transition은 제스처 인식기에서 현재 팬 동작의 이동 거리를 가져옵니다. 이 거리는 사용자가 화면에서 손가락을 얼마나 이동했는지를 나타냅니다.                let translation = gesture.translation(in: sceneView)                let newPosition = SCNVector3(                    x: originalNodePosition.x + Float(translation.x * 0.05),                    y: originalNodePosition.y + Float(translation.y * -0.05),                    z: originalNodePosition.z + Float(translation.y * -0.05)                )                /*                 - `translation` 값을 이용하여 `newPosition`을 계산합니다.                 - `originalNodePosition.x`에 `translation.x`을 더하여 노드의 새로운 x 위치를 계산합니다. `translation.x`에 0.001을 곱한 것은 화면상의 이동 거리를 3D 공간상의 이동 거리로 변환하기 위함입니다.                 - `originalNodePosition.y`는 그대로 유지됩니다. 이는 노드의 높이(y 축)를 변경하지 않음을 의미합니다.                 - `originalNodePosition.z`에 `translation.y`을 더하여 노드의 새로운 z 위치를 계산합니다. 마찬가지로 `translation.y`에 0.001을 곱한 것은 화면상의 이동 거리를 3D 공간상의 이동 거리로 변환하기 위함입니다.                 */                selectedNode.position = newPosition
                // addAnimation(node: selectedNode)                // addMoveUpDownAnimation(node: selectedNode)            }        case .ended, .cancelled:            selectedNode = nil            originalNodePosition = nil        default:            break        }    }</code></pre>
<p>추가로 해당 코드에서 사용되는 개념 : translation / SCNVector3 을 아래에서 추가 설명합니다(공식문서 설명)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번외]SceneKit- 기본 Tech 설명 ]]></title>
            <link>https://velog.io/@hyeonhee_bee/SceneKit-%EA%B8%B0%EB%B3%B8-Tech-%EC%84%A4%EB%AA%85</link>
            <guid>https://velog.io/@hyeonhee_bee/SceneKit-%EA%B8%B0%EB%B3%B8-Tech-%EC%84%A4%EB%AA%85</guid>
            <pubDate>Mon, 14 Apr 2025 11:02:15 GMT</pubDate>
            <description><![CDATA[<h1 id="scenekit">SceneKit</h1>
<ul>
<li><a href="https://developer.apple.com/documentation/scenekit#1613385">공식문서를</a> 참조했습니다. </li>
</ul>
<p>3D 게임을 만들고 앱에 3D 콘텐츠를 추가하고 애니메이션, 물리 시뮬레이션, 파티클 효과 및 사실적인 물리 기반 렌더링을 쉽게 추가할 수 있습니다.</p>
<blockquote>
<p><a href="https://developer.apple.com/documentation/scenekit/scnscene">class SCNScene</a> SCNScene 클래스
표시 가능한 3D 씬을 구성하는 노드 계층 구조 및 전역 프로퍼티를 위한 컨테이너입니다.
<a href="https://developer.apple.com/documentation/scenekit/scnview">class SCNView</a> SCNView 클래스
3D SceneKit 콘텐츠를 표시하기 위한 뷰입니다.
class SCNNode SCNNode 클래스
지오메트리, 조명, 카메라 또는 기타 표시 가능한 콘텐츠를 첨부할 수 있는 3D 좌표 공간의 위치와 변환을 나타내는 씬 그래프의 구조적 요소입니다.</p>
</blockquote>
<p><a href="https://developer.apple.com/documentation/scenekit#1613385"><strong>Scene Structure</strong></a></p>
<p><a href="https://developer.apple.com/documentation/scenekit/organizing_a_scene_with_nodes">Organizing a Scene with Nodes</a> 노드로 씬 구성하기 : 노드를 사용하여 씬의 구조를 정의합니다.</p>
<p>SceneKit은 콘텐츠를 노드의 계층적 트리 구조(씬 그래프라고도 함)로 구현합니다. 씬은 씬의 월드 좌표 공간을 정의하는 루트 노드와 보이는 콘텐츠로 월드를 채우는 다른 노드로 구성됩니다.</p>
<p>SceneKit은 아래 그림과 같이 기본적으로 뷰 방향이 음의 z축을 따르는 오른손 좌표계를 사용합니다.</p>
<p><strong>SCN노드</strong>
지오메트리, 조명, 카메라 또는 기타 표시 가능한 콘텐츠를 첨부할 수 있는 3D 좌표 공간의 위치와 트랜스폼을 나타내는 장면 그래프의 구조적 요소입니다.</p>
<p>SCN노드 객체 자체는 해당 객체가 포함된 씬이 렌더링될 때 표시되는 콘텐츠가 없으며 부모 노드를 기준으로 좌표 공간 변환(위치, 방향, 배율)만 나타냅니다. 씬을 구성하려면 노드 계층 구조를 사용하여 구조를 만든 다음 조명, 카메라 및 지오메트리를 노드에 추가하여 표시되는 콘텐츠를 만듭니다.
노드는 씬의 구조를 결정합니다. SceneKit을 사용하여 프로그래밍 방식으로 노드 계층 구조를 만들거나, 3D 제작 툴을 사용하여 만든 파일에서 계층 구조를 로드하거나, 두 가지 방법을 결합할 수 있습니다
노드의 어태치먼트가 시각적 콘텐츠와 동작을 정의합니다.노드 계층 구조는 씬의 공간적, 논리적 구조를 결정하지만 보이는 콘텐츠는 결정하지 않습니다.</p>
<p><a href="https://developer.apple.com/documentation/scenekit/scnreferencenode"><code>class SCNReferenceNode</code></a>SCNReferenceNode 클래스
별도의 씬 파일에서 로드할 콘텐츠의 플레이스홀더 역할을 하는 씬 그래프 노드입니다.
참조 노드(SCNReferenceNode) 에 콘텐츠를 로드하도록 지시하면 SceneKit은 참조된 파일(referenced scene file)을 로드하고 파일의 루트 노드의 자식(scene file’s root node)을 참조 노드의 자식(children of the reference node.)이 되게 합니다.</p>
<p>r<strong>ootNode 루트 노드 : 씬 그래프의 루트 노드</strong><code>var rootNode: [SCNNode](https://developer.apple.com/documentation/scenekit/scnnode) { get }</code></p>
<p>모든 씬 콘텐츠(노드, 지오메트리 및 해당 머티리얼, 조명, 카메라 및 관련 오브젝트)는 하나의 공통 루트 노드가 있는 노드 계층 구조로 구성됩니다.</p>
<p>이어서 , SceneKit 의 핵심요소 세가지, SCNView, SCNScene 과 SCNNode에 대해 설명하는 부분이 이어집니다.</p>
<h1 id="1-scnscene">(1) SCNScene</h1>
<p>표시 가능한 3D 씬을 구성하는 노드 계층구조와 글로벌 프로퍼티를 위한 컨테이너입니다.</p>
<p>SceneKit으로 3D 콘텐츠를 표시하려면 시각적 요소를 함께 나타내는 노드 및 속성의 계층 구조가 포함된 장면을 만듭니다.
씬을 표시하려면 씬을 로드한 다음 SCNView의 씬 프로퍼티로 설정해야 합니다:</p>
<pre><code class="language-swift">guard let myScene = SCNScene(named: &quot;MyScene&quot;)
    else { fatalError(&quot;Unable to load scene file.&quot;) }scnView.scene = myScene // Your app&#39;s SCNView</code></pre>
<h3 id="arworldtrackingconfiguration">ARWorldTrackingConfiguration</h3>
<p>환경에 놓인 오브젝트와 관련하여 디바이스의 위치를 추적하는 구성입니다.</p>
<p>ARWorldTrackingConfiguration 클래스는 3개의 회전 축(롤, 피치, 요)과 3개의 이동 축(x, y, z 방향의 움직임)의 6자유도(6DOF)로 디바이스의 움직임을 추적합니다.
이러한 트래킹을 통해 몰입감 넘치는 AR 환경을 만들 수 있습니다: 사용자가 디바이스를 기울여 물체의 위나 아래를 보거나 디바이스를 움직여 물체의 측면과 뒷면을 보더라도 가상 물체는 현실 세계와 동일한 위치에 있는 것처럼 보일 수 있습니다.
후면 카메라를 사용하여 AR 환경을 표시하는 앱을 실행할때 해당 구성에 따라 세가지 옵션이 가능합니다.</p>
<p><strong>월드 트래킹 세션이 카메라에 보이는 실제 장면의 요소를 인식하거나 상호 작용하는 방법</strong></p>
<ol>
<li>planeDetection : 실제 세계의 수평 또는 수직 표면을 찾습니다. 표면을 ARPlaneAnchor 오브젝트로 세션에 추가합니다.</li>
<li>detectionImages : 2D 이미지의 움직임을 인식하고 추적합니다. 2D 이미지를 ARImageAnchor 오브젝트로 장면에 추가합니다.</li>
<li>detectionObjects: 3D 객체를 인식합니다. 3D 오브젝트를 ARObjectAnchor 오브젝트로 장면에 추가합니다.</li>
</ol>
<h3 id="arconfiguration">ARConfiguration</h3>
<p>증강 현실 세션을 구성하는 방법에 대한 정보가 포함된 기본 객체입니다.</p>
<p>ARConfiguration은 AR 경험에서 구성할 수 있는 다양한 옵션에 대한 기본 클래스를 정의합니다. 앱에서 가상 콘텐츠와 라이브 카메라 이미지를 혼합하면 사용자는 가상 콘텐츠가 실제 세계의 일부인 것처럼 착각하게 됩니다.</p>
<ul>
<li><a href="https://developer.apple.com/documentation/arkit/arconfiguration/3089121-framesemantics#3235008">*Enable People Occlusion</a>* 피플 오클루전은 카메라 피드에 있는 사람이 앱의 가상 콘텐츠를 가릴 수 있도록 하는 기능입니다.
(1) 사람이 가상 콘텐츠보다 카메라에 더 가까이 있을 때 앱의 가상 콘텐츠와 겹치도록 할때</li>
</ul>
<p>구성의 프레임 시맨틱에 personSegmentationWithDepth 옵션을 추가합니다.</p>
<pre><code class="language-swift">if let config = mySession.configuration as? ARWorldTrackingConfiguration {    config.frameSemantics.insert(.personSegmentationWithDepth)    // Run the configuration to effect a frame semantics change.    mySession.run(config)}</code></pre>
<ol>
<li>가상 객체 앞에 있는 사람은 가상 객체를 가리고, 가상 객체 뒤에 있는 사람은 가상 객체에 의해 가려지게 하고싶을때</li>
</ol>
<p>장면에서 사람의 깊이와 관계없이 앱의 가상 콘텐츠에 사람이 겹쳐야 함을 나타내려면 대신 personSegmentation 프레임 시맨틱을 사용합니다. 이 옵션은 특히 그린 스크린 시나리오에 적합합니다.</p>
<h1 id="2scnview">(2)SCNView</h1>
<h3 id="scnview">SCNView</h3>
<p>3D SceneKit 콘텐츠를 표시하기 위한 뷰입니다.</p>
<p>macOS에서 SCNView는 NSView의 서브클래스입니다. iOS 및 tvOS에서 SCNView는 UIView의 서브클래스입니다. 두 운영 체제의 뷰 계층 구조의 일부인 SCNView 오브젝트는 앱의 사용자 인터페이스에서 SceneKit 콘텐츠를 위한 공간을 제공합니다. SceneKit 보기에 콘텐츠를 제공하려면 해당 씬 프로퍼티에 SCNScene 객체를 할당합니다.
SceneKit 뷰 작업에 대한 추가 중요 메서드 및 속성은 SCNSceneRenderer 프로토콜을 참조하십시오. (SCNRenderer 클래스를 사용하여 SceneKit 콘텐츠를 임의의 Metal 명령 대기열 또는 OpenGL 컨텍스트에 렌더링하거나 SCNLayer 클래스를 사용하여 macOS에서 코어 애니메이션 레이어로 렌더링할 수도 있습니다. SCNSceneRenderer 프로토콜은 세 가지 SceneKit 렌더링 클래스 모두에 공통된 기능을 정의합니다.)</p>
<p><a href="https://developer.apple.com/documentation/scenekit/scnview#1654723"><strong>Specifying a Scene</strong></a></p>
<p><a href="https://developer.apple.com/documentation/scenekit/scnview/1523904-scene"><code>var scene: SCNScene?</code></a>
뷰에 표시할 장면입니다.</p>
<h1 id="3-scnnode">(3) SCNNode</h1>
<p>지오메트리, 조명, 카메라 또는 기타 표시 가능한 콘텐츠를 첨부할 수 있는 3D 좌표 공간의 위치와 트랜스폼을 나타내는 장면 그래프의 구조적 요소입니다.</p>
<p>SCN노드 오브젝트는 그 자체가 포함된 씬이 렌더링될 때 보이는 콘텐츠가 없으며, 부모 노드를 기준으로 좌표 공간 변환(위치, 방향 및 크기)만 나타냅니다. 씬을 구성하려면 노드 계층 구조를 사용하여 구조를 만든 다음 조명, 카메라 및 지오메트리를 노드에 추가하여 표시되는 콘텐츠를 만듭니다.</p>
<ul>
<li><a href="https://developer.apple.com/documentation/scenekit/scnnode#1654929">*Nodes Determine the Structure of a Scene</a>* 노드는 씬의 구조를 결정합니다
씬의 노드 계층 구조 또는 씬 그래프는 콘텐츠의 구성과 SceneKit을 사용하여 해당 콘텐츠를 표시하고 조작하는 기능을 모두 정의합니다. SceneKit을 사용하여 프로그래밍 방식으로 노드 계층 구조를 만들거나, 3D 제작 툴을 사용하여 만든 파일에서 계층 구조를 로드하거나, 두 가지 방법을 결합할 수 있습니다. 자세한 내용은 노드 계층 구조 관리 <a href="https://developer.apple.com/documentation/scenekit/scnnode#1655182">Managing the Node Hierarchy</a> 및 노드 계층 구조 검색의 메서드 <a href="https://developer.apple.com/documentation/scenekit/scnnode#1655236">Searching the Node Hierarchy</a> 를 참조하십시오.</li>
</ul>
<p>씬의 루트노드 오브젝트  <a href="https://developer.apple.com/documentation/scenekit/scnscene/1524029-rootnode"><code>rootNode</code></a>  는 SceneKit에서 렌더링되는 월드의 좌표계를 정의합니다. 이 루트 노드에 추가하는 각 자식 노드는 자체 좌표계를 생성하며, 이 좌표계는 차례로 자식 노드에 상속됩니다. 노드의 위치, 회전 및 배율 속성  <a href="https://developer.apple.com/documentation/scenekit/scnnode/1408026-position"><code>position</code></a>, <a href="https://developer.apple.com/documentation/scenekit/scnnode/1408034-rotation"><code>rotation</code></a>, and <a href="https://developer.apple.com/documentation/scenekit/scnnode/1408050-scale"><code>scale</code></a> 을 사용하여(또는 트랜스폼 속성  <a href="https://developer.apple.com/documentation/scenekit/scnnode/1407964-transform"><code>transform</code></a>을 직접 사용하여) 좌표계 간의 변환을 결정합니다.</p>
<ul>
<li><a href="https://developer.apple.com/documentation/scenekit/scnnode#1654943">*A Node’s Attachments Define Visual Content and Behavior</a>* 노드의 어태치먼트는 시각적 콘텐츠와 동작을 정의</li>
</ul>
<p>노드 계층 구조는 씬의 공간적, 논리적 구조를 결정하지만 보이는 콘텐츠는 결정하지 않습니다. 노드에 SCNGeometry <a href="https://developer.apple.com/documentation/scenekit/scngeometry"><code>SCNGeometry</code></a>오브젝트를 첨부하여 씬에 2D 및 3D 오브젝트를 추가할 수 있습니다. (지오메트리는 차례로 모양을 결정하는 SCNMaterial <a href="https://developer.apple.com/documentation/scenekit/scnmaterial"><code>SCNMaterial</code></a> 오브젝트를 첨부합니다.)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[5편] 팬/핀치 제스처로 오브젝트 이동/확대하기 & 정리]]></title>
            <link>https://velog.io/@hyeonhee_bee/5%ED%8E%B8-%ED%8C%AC%ED%95%80%EC%B9%98-%EC%A0%9C%EC%8A%A4%EC%B2%98%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EB%8F%99%ED%99%95%EB%8C%80%ED%95%98%EA%B8%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hyeonhee_bee/5%ED%8E%B8-%ED%8C%AC%ED%95%80%EC%B9%98-%EC%A0%9C%EC%8A%A4%EC%B2%98%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%9D%B4%EB%8F%99%ED%99%95%EB%8C%80%ED%95%98%EA%B8%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 14 Apr 2025 11:00:43 GMT</pubDate>
            <description><![CDATA[<h3 id="🍰-다섯번째-코드조각">🍰 다섯번째 코드조각</h3>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/535b0cb2-1347-413e-8a12-a6cc04f2ee69/image.png" alt=""></p>
<p>두번째 제스쳐인 팬 제스쳐 코드입니다. 마찬가지로 최상단 노드를 찾아 selectedNode로 설정하는 것은 같으나 오브젝트 전체를 드래그 즉 팬제스쳐를 통해 이동시키기 위해서는 최상단 노드를 찾는것으로 충분하기 때문에 추가로 자식노드를 찾는과정은 없습니다. 최상단 노드 , 즉 컨트롤 하기위해 필요한 노드를 찾았다면 팬제스쳐가 시작될때의 위치를 originalNodePosition 변수에서 저장해두었다가, 팬제스쳐가 실행, 즉 changed될때 sceneView에서 제스쳐가 변화된 만큼을 originalNodePosition기준으로 적절하게 계산하여 새로운 위치로 조정해줍니다. 여기서는 0.05를 곱하였고 z 축의 경우에는 따로 2D 좌표계인 제스쳐에서는 계산이 불가하여 적절한 위치로 보이도록 기존 z 축 위치에 y축 변화량을 계산해주는 방식으로 보완하였습니다.</p>
<p>제스처가 끝나는 ended 와 cancelled 의 경우에는 노드와 포지션의 값을 nil로 초기화 시켜줍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[4편] 제스처: 복잡한 노드 반복 제어하기]]></title>
            <link>https://velog.io/@hyeonhee_bee/4%ED%8E%B8-%EC%A0%9C%EC%8A%A4%EC%B2%98-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EB%85%B8%EB%93%9C-%EB%B0%98%EB%B3%B5-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/4%ED%8E%B8-%EC%A0%9C%EC%8A%A4%EC%B2%98-%EB%B3%B5%EC%9E%A1%ED%95%9C-%EB%85%B8%EB%93%9C-%EB%B0%98%EB%B3%B5-%EC%A0%9C%EC%96%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 10:59:39 GMT</pubDate>
            <description><![CDATA[<h3 id="🍰-네번째-코드조각">🍰 네번째 코드조각</h3>
<p>유사한 과정을 거칩니다. 다만 여기서는 불꽃노드가 Feu01 부터 Feu64까지 복잡한 노드구성임을 확인했기 때문에 for 문을 통해 해당 노드들을 찾고 노드를 on/off 하는 로직으로 구성했습니다.</p>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/8189dbc4-55b8-4c9d-81b2-d704553ed2ce/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[3편] 제스처: 탭으로 오브젝트 조작하기]]></title>
            <link>https://velog.io/@hyeonhee_bee/3%ED%8E%B8-%EC%A0%9C%EC%8A%A4%EC%B2%98-%ED%83%AD%EC%9C%BC%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/3%ED%8E%B8-%EC%A0%9C%EC%8A%A4%EC%B2%98-%ED%83%AD%EC%9C%BC%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 10:58:41 GMT</pubDate>
            <description><![CDATA[<h3 id="🍰-세번째-코드조각">🍰 세번째 코드조각</h3>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/08a95cbb-4db6-4318-86d7-62e257fc6c24/image.png" alt="">
제스쳐를 활용하는 예시 두가지를 설명하겠습니다.첫번째는 탭제스쳐를 통해 노드를 찾아서 숨기는 기능입니다. 노드가 만약 프로젝트에서처럼 불꽃이라면 노드를 숨김으로써 불꽃이 꺼지고 켜지는 애니메이션을 구현할 수 있습니다. 첫번째 코드줄에서 sceneView에서의 제스쳐의 위치를 찾고, 히트테스트 메서드를 실행한 후에 테스트로 찾은 결과값을 hitResult의 복수형인 hitResults로 저장합니다. 히트 테스트는 주어진 2D 화면 좌표(여기서는 제스쳐를 통한 로케이션 좌표입니다) 에서 3D 공간의 객체를 검색하는 메서드로 히트테스트의 결과로 화면에서 가장 가까운 객체부터 차례로 배열에 담깁니다. hitResults 는 <code>SCNHitTestResult</code> 객체의 배열이 되고 장면 요소에 대한 장면 공간 또는 뷰 공간 검색 결과에 대한 정보가 담겨있습니다.</p>
<p>해당 배열의 첫 번째 요소를 가져와서 <code>hitResult</code>로 저장합니다. 첫 번째 요소는 화면에서 가장 가까운 객체를 나타냅니다. hitResult 요소에서 node 정보를 가져와서 selectedNode에 저장합니다.</p>
<p>이렇게 찾은 selectedNode 가 최상위 노드가 될때까지 while문을 반복한 후에 최상위 노드를 찾고 selectedNode로 설정하였으면 이제부터는 내가 찾는 노드이름이 나올때까지 childNode로 찾아 내려갑니다. 여기서는 Cone_3하위의 Object_4노드임을 scn파일 구조를 통해 확인했기 때문에 해당 노드를 찾습니다. candle의 불꽃을 찾아서 해당 노드가 hidden 상태라면 탭제셔쳐를 통해 보여지도록 보여지지않는 꺼진 상태라면 눌러서 켜지도록 로직을 구현했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2편] 오브젝트 불러오기 & SCNReferenceNode 이해하기]]></title>
            <link>https://velog.io/@hyeonhee_bee/2%ED%8E%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-SCNReferenceNode-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/2%ED%8E%B8-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0-SCNReferenceNode-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 10:57:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/be69c8c8-ef42-4907-aaf0-421c1e90287a/image.png" alt=""></p>
<h3 id="🍰-두번째-코드조각">🍰 두번째 코드조각</h3>
<p>이제 씬을 올렸으니 위에 scenekit을 활용하여 노드를 위치시켜 오브젝트를 올릴 수 있습니다</p>
<p>여기서는 usdz 확장자를 가진 모델을 업로드하는 방법을 설명합니다.</p>
<p>해당 프로젝트에서는 우선 인벤토리에서 오브젝트를 꺼낼때마다 새로운 오브젝트를 위치시켜야 하므로 기존 오브젝트, 즉 노드를 삭제하도록 합니다. removeFromParentNode메서드를 통해 루트노드의 모든 자식노드를 제거해서 불필요한 기존 노드를 모두 삭제합니다. 이제 새로운 오브젝트를 올리기 위한 코드입니다.<code>Bundle.main.url(forResource:withExtension:)</code> 메서드를 사용하여 art.scnasset폴더내에 지정된 이름과 확장자를 가진 USDZ 파일의 URL을 가져온 후에 <code>SCNReferenceNode(url: url)</code>를 사용하여 해당 url 을 통해 SCNReferenceNode 를 만들고 node 에 저장합니다.이렇게 참조노드 형태로 만들면 방금과 같이 url을 통해 객체 인스턴스가 생성되는 즉시 로드되는것이 아닌 하단의 node.load() 함수처럼 load()메서드가 호출될때 콘텐츠가 로딩됩니다. 외부파일 즉, usdz파일들의 노드들은 이 참조노드의 자식노드가 됩니다. 그리고 가장 마지막 코드인 sceneView.scene.rootNode.addChildNode 를 통해 외부 usdz파일들의 노드를 가지고 있는 방금 생성한 참조노드 즉 node를 sceneView의 루트노드의 자식노드로 설정하면 씬에 위치시킬 수 있게 됩니다.</p>
<p>이 노드를 루트노드에 연결하기 전에 position과 scale 혹은 transform등의 속성을 통해 노드의 위치를 부수적으로 설정해줄 수 도 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[1편] ARKit 기초 설정 및 SCNScene 개념 소개]]></title>
            <link>https://velog.io/@hyeonhee_bee/1%ED%8E%B8-ARKit-%EA%B8%B0%EC%B4%88-%EC%84%A4%EC%A0%95-%EB%B0%8F-SCNScene-%EA%B0%9C%EB%85%90-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@hyeonhee_bee/1%ED%8E%B8-ARKit-%EA%B8%B0%EC%B4%88-%EC%84%A4%EC%A0%95-%EB%B0%8F-SCNScene-%EA%B0%9C%EB%85%90-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Mon, 14 Apr 2025 10:54:53 GMT</pubDate>
            <description><![CDATA[<h3 id="👩💻키노트용-코드조각-설명-코드조각을-더-단순화-했고-설명을-줄글로-읽을-수-있습니다">👩‍💻키노트용 코드조각 설명 (코드조각을 더 단순화 했고 설명을 줄글로 읽을 수 있습니다)</h3>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/cf42e18b-6736-4668-b434-6e90e8e8403a/image.png" alt=""></p>
<h3 id="🍰-첫번째-코드조각">🍰 첫번째 코드조각</h3>
<p>ARSCNView 클래스는 가상 3D 콘텐츠와 현실 세계의 기기 카메라 뷰를 혼합하는 증강 현실 경험을 만들기 위해 쓰이는 클래스입니다. 뷰에 제공된 ARSession 객체를 실행하면 sceneView는 자동으로 아이폰의 카메라 촬영장면을 앱의 배경으로 렌더링합니다. ARKit은 SceneKit의 좌표 공간을 실제 세계와 자동으로 일치시키기때문에 추후 씬킷에서 업로드하는 오브젝트와 ARKit 으로 구성된 카메라 뷰가 동적으로 반응하여 현실감을 더해줍니다. 앵커를 직접 생성하여 세션에 추가하거나 혹은 ARKit이 자동으로 생성하는 앵커를 사용하여 오브젝트를 씬에 위치할 수 있습니다. 해당 뷰를 main스토리보드에 깔고 해당 뷰를 뷰컨트롤러가 제어할수 있도록 ViewController 클래스 내부의 viewDidLoad 함수를 재정의할때 scene.delegate = self 로 설정합니다.</p>
<p>그리고 SCNScene 클래스의 인스턴스를 생성합니다. SCNScene 은 추후 3D 오브젝트를 올릴때 필요한 노드를 쌓기 위해 필요한 일종의 컨테이너 입니다. 노드가 올라가는 씬이라고 이해하면 좋습니다. 해당 씬이 동작하려면 SCNView의 scene 프로퍼티에 해당 씬을 설정해야합니다. scene프로퍼티는 뷰에 표시할 SceneKit 장면을 의미합니다. 따라서 sceneView.scene = scene 로 런타임에 노드들이 올라간 SCNScene씬이 표시되게 합니다.</p>
<p>viewWillAppear 재정의 함수 내에서는 ARkit 의 세션을 configuration으로 설정하고 run 메서드로 실행합니다. ARWorldTrackingConfiguration은 AR환경에 위치한 오브젝트와 실제 장면 요소의 인식과 관련된 설정을 진행할 수 있습니다. 여기서는 personSegmentation 시멘틱으로 설정하여 만약 두 사람이 있고 그 사이에 가상 객체가 있는 상황이라면 가상 객체 앞에 있는 사람은 가상 객체를 가리고, 가상 객체 뒤에 있는 사람은 가상 객체에 의해 가려지는 자연스러운 깊이감을 줄 수 있도록 셋팅하는 코드입니다.</p>
<p>해당 설정을 기반으로 세션을 실행합니다. 여기서 sceneView의 session프로퍼티는 뷰의 콘텐츠에 대한 모션 트래킹 및 카메라 이미지 처리를 관리하는 AR 세션을 의미하고 run 메서드로 실행가능합니다.</p>
<p>viewWillDisappear 재정의 함수내에서는 session의 pause 메서드가 실행되도록 하여 AR 세션을 일시 중지하여 불필요한 리소스 활용을 방지합니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[개요]SwiftUI / SceneKit / ARKit 로 오브젝트 올리고 제스쳐 적용하기
]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EA%B0%9C%EC%9A%94SwiftUI-SceneKit-ARKit-%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%98%AC%EB%A6%AC%EA%B3%A0-%EC%A0%9C%EC%8A%A4%EC%B3%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyeonhee_bee/%EA%B0%9C%EC%9A%94SwiftUI-SceneKit-ARKit-%EB%A1%9C-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%98%AC%EB%A6%AC%EA%B3%A0-%EC%A0%9C%EC%8A%A4%EC%B3%90-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 14 Apr 2025 10:52:05 GMT</pubDate>
            <description><![CDATA[<p>아카데미 NC2 프로젝트때의 미션인 2주만에 키트를 공부하고, 프로젝트완성하기의 일환으로 ARKit, SceneKit 를 배웠습니다. (참고로 아카데미가 끝난 후 앱이 배포 완료된 상태입니다) 
이때 정리해서 아카데미내에 공유했던 글을 정리해서 배포합니다 :) </p>
<p>총 5편+9편 으로 구성 되어있습니다.
​</p>
<p>1) 5편의 시리즈가 끝난 후에는 
2) 기능별 코드를 한줄한줄 주석을 포함해 9편(8+보너스)으로 공유할 예정입니다. 
​
[코드조각으로 이해하기]
 [1편] ARKit 기초 설정 및 SCNScene 개념 소개
첫 번째 코드 조각 + AR 환경 설정 코드 + SCNScene 설명
 [2편] 오브젝트 불러오기 &amp; SCNReferenceNode 이해하기
두 번째 코드 조각 + 오브젝트 올리는 코드 + SCNNode 개념
 [3편] 제스처: 탭으로 오브젝트 조작하기
세 번째 코드 조각 + 탭 제스처 핸들링
 [4편] 제스처: 복잡한 노드 반복 제어하기
네 번째 코드 조각 + 롱프레스 제스처 핸들링 + Feu노드 컨트롤 예시
 [5편] 팬/핀치 제스처로 오브젝트 이동/확대하기 &amp; 정리
다섯 번째 코드 조각 + 팬/핀치 제스처 핸들링 
​
[기능별 코드(주석포함)으로 이해하기]
[1편][AR 환경 설정을 위한 기본 구성]
ARKit과 SceneKit을 결합한 View 설정
ARWorldTrackingConfiguration을 통한 디바이스 위치 및 방향 추적
사람 인식 및 객체 감지를 위한 프레임 시맨틱 설정
AR 세션 초기화 및 기존 트래킹 정보 제거
​
[2편][SCNScene과 SCNView 초기 설정]
SCNScene 인스턴스 생성
SCNView에 Scene을 바인딩
인벤토리 버튼, 제스처 등록 준비
​
[3편][USDZ 오브젝트 불러오기 및 배치]
기존 노드 제거
SCNReferenceNode를 사용한 외부 모델 불러오기
모델 위치 조정 및 스케일 설정
씬에 노드 추가
조명 재설정 및 사운드 실행 포함
​
[4편][제스처 인식기 등록]
탭(Tap) 제스처
팬(Pan) 제스처
핀치(Pinch) 제스처
롱프레스(LongPress) 제스처
​
[5편][제스처 처리 함수 구현]
팬 제스처로 오브젝트 이동
핀치 제스처로 오브젝트 크기 조정
탭 제스처로 오브젝트 숨기기/보이기
롱프레스 제스처로 오브젝트 애니메이션 실행
​
[6편][SceneKit의 핵심 클래스 개념 정리]
SCNScene: 3D 씬 구조 및 속성 컨테이너
SCNView: 3D 콘텐츠를 렌더링하는 뷰
SCNNode: 씬 구조를 구성하는 기본 단위
SCNReferenceNode: 외부 파일을 참조하여 노드 불러오기
​
[7편][AR 구성 관련 클래스와 설정 설명]
ARWorldTrackingConfiguration을 통한 6자유도(6DOF) 추적
ARConfiguration의 기본 설정 개념
frameSemantics로 사람 인식 추가 옵션 설정
​
[8편][SceneKit 애니메이션 처리 기초]
SCNTransaction을 활용한 애니메이션 트랜잭션 처리
setAnimationDuration을 통한 암시적 애니메이션 적용
커스텀 트랜잭션으로 고급 애니메이션 구성
SCNVector3 구조체를 사용한 위치·크기 계산
​
[보너스][제스처 구현 시 참고할 팁과 세부 동작 이해]
hitTest를 사용하여 터치 지점의 노드 탐지
선택된 노드의 최상위 부모 노드를 찾아 전체 모델 이동 가능하게 설정
translation 값을 활용한 위치 변화 계산
delaysTouchesBegan 속성으로 터치 이벤트 처리 방식 조절
제스처 상태(state)에 따른 분기 처리: began, changed, ended, cancelled</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주니어의 역량]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-%EC%97%AD%EB%9F%89</link>
            <guid>https://velog.io/@hyeonhee_bee/%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%98-%EC%97%AD%EB%9F%89</guid>
            <pubDate>Tue, 09 Apr 2024 05:10:09 GMT</pubDate>
            <description><![CDATA[<p>요즘 처럼 스펙과 스킬들이 넘쳐날때 어떤 자세를 갖춰야하는지..어떤 속도로 어떤것들을 중점적으로 봐야하는지 보노와 이야기를 나눴다.
너무 좋고 감사한시간이었음 </p>
<p>초심을 잃지 않게 리마인드 되었던 시간이었기도하고, 여러모로 좋았다. </p>
<p>까먹지 않으려고 오픈된 곳에 남겨본다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀의 협력, 지속성은 어디에서 올까?]]></title>
            <link>https://velog.io/@hyeonhee_bee/%ED%8C%80%EC%9D%98-%ED%98%91%EB%A0%A5-%EC%A7%80%EC%86%8D%EC%84%B1%EC%9D%80-%EC%96%B4%EB%94%94%EC%97%90%EC%84%9C-%EC%98%AC%EA%B9%8C</link>
            <guid>https://velog.io/@hyeonhee_bee/%ED%8C%80%EC%9D%98-%ED%98%91%EB%A0%A5-%EC%A7%80%EC%86%8D%EC%84%B1%EC%9D%80-%EC%96%B4%EB%94%94%EC%97%90%EC%84%9C-%EC%98%AC%EA%B9%8C</guid>
            <pubDate>Sat, 06 Apr 2024 02:26:09 GMT</pubDate>
            <description><![CDATA[<p>러너들과 우연히 이야기할 기회가 있어서 얘기해본 주제</p>
<p>애정은 프로젝트를 지속하게 한다. </p>
<p>팀원에 대한 애정 
프로젝트에 대한 애정</p>
<p>프로젝트에 대한 애정은 목표 얼라인의 중요성과도 연결된다. 
팀빌딩 과정이 중요하다고 생각하는 이유중에 하나이다. 결국 공통된 비전과 목표를 가지고 있을때 해당 프로젝트에 대한 애정도 생기기 때문이다. 
진행할 수록 내가 바라던 목표와 멀어지는 프로젝트라면 남아있던 애정마저 사라지기 쉽상일 것이다. </p>
<p>이와 비슷하게 프로젝트에 대한 애정없이도 팀원에 대한 애정으로 프로젝트가 지속되는 경우도 있을 것이다. 
즉 팀에 대한 애정이 프로젝트를 지속하게 하는 이유라는 것이다. </p>
<p>짧은 경험이지만, 회사를 다니면서 내가 느꼈던 점은 팀에 애정을 갖게하는것도 그 팀의 역량이라는 것이다. 
목표에 얼라인 할 수 있도록 끊임 없이 소통하는 것, 각자의 소통방식을 존중하면서 소통하는 것, 워킹스타일을 존중하면서 워킹하는 것, 그리고 팀 자체에 대한 애정을 지속할 수 있도록 소통하는 것. 이 모든것이 그 자체로도 팀의 역량이다. </p>
<p>훌륭한 팀은 개인을 적재적소에 잘 활용되게 만든다. 서로 윈윈하게 만든다. 애정을 주고받고 팀이 지속할 수 있도록 한다. 그리하여 결국 done 이 되도록 한다. </p>
<p>이러한 과정은 팀의 역량에 따른 것이다. 지속성 부족에 대한 문제는 단순한 개인의 일탈이나 개인의 역량의 부족만으로는 설명되지 않을때가 많다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파이썬 사내 프로젝트 계속]]></title>
            <link>https://velog.io/@hyeonhee_bee/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%82%AC%EB%82%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B3%84%EC%86%8D</link>
            <guid>https://velog.io/@hyeonhee_bee/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%82%AC%EB%82%B4-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B3%84%EC%86%8D</guid>
            <pubDate>Sun, 17 Dec 2023 13:57:54 GMT</pubDate>
            <description><![CDATA[<ol>
<li>Qt 디자이너로 버튼 2개추가 후 export 기능 구현</li>
<li>임의로 필터링해서 해당 export하는 엑셀파일에 모아놓는 기능 구현 </li>
</ol>
<p>*<em>앞으로 해야할 것 *</em></p>
<p>임의로 리터럴한 값을 코드에 박아서 필터링하는게 아니라 GUI에서 분류나 조건등을 선택해서 필터링한 값들을 화면에 보여줘야 함. </p>
<p>오랜만에 긴 시간 코딩을 했다. 물론 삽질이었지만... </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift TIL-알고리즘 스터디]]></title>
            <link>https://velog.io/@hyeonhee_bee/Swift-TIL-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@hyeonhee_bee/Swift-TIL-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Thu, 23 Nov 2023 16:05:33 GMT</pubDate>
            <description><![CDATA[<p>복직에 정신 못차리는 주간들</p>
<p>알고리즘 스터디라도 하고 있어 다행이다 내가 맡은 부분은 삽입정렬 알고리즘이었다. 
선택, 버블, 퀵, 카운팅 솔트를 배울 수 있었는데 시간복잡도와 공간복잡도의 개념, 그리고 각각의 장단점을 느끼고 구현방법을 익힐 수 있어 유익했다 :) </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift TIL(74)- 알고리즘 스터디]]></title>
            <link>https://velog.io/@hyeonhee_bee/Swift-TIL%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94</link>
            <guid>https://velog.io/@hyeonhee_bee/Swift-TIL%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%8A%A4%ED%84%B0%EB%94%94</guid>
            <pubDate>Thu, 16 Nov 2023 14:07:13 GMT</pubDate>
            <description><![CDATA[<p>오늘도 너무 재밌었던 알고리즘 스터디 
복직 첫주라는 핑계로 문제는 못풀어갔는데 다음에는 꼭 한문제 이상 풀어갈 것</p>
<h3 id="이름궁합">이름궁합</h3>
<ul>
<li>딕셔너리 사용</li>
<li>인덱스 당기기</li>
<li>유니코드 스칼라 값구하기</li>
</ul>
<p><a href="https://demian-develop.tistory.com/30">시간복잡도</a></p>
<p>sort, sorted 굳
메모리 두배되는 단점이 있을지도..?</p>
<h3 id="듣보잡">듣보잡</h3>
<ul>
<li>Array.contains =&gt; O(n)</li>
<li>Set.contains =&gt; O(1)</li>
</ul>
<p>Set이 유용한 예시</p>
<h3 id="염색체">염색체</h3>
<ul>
<li>if 대신 가드문사용</li>
<li>함수화 oneOrMore 함수로 체크</li>
</ul>
<h3 id="베스트셀러">베스트셀러</h3>
<ul>
<li>swift dictionary updatevalue</li>
<li>팔린책이 여러개일때 사전순으로 정렬할때 max 클로져</li>
<li>딕셔너리[readLine()!, default: 0] += 1</li>
</ul>
<p>간지는 프로그래머스에서</p>
<p><a href="https://developer.apple.com/documentation/swift/dictionary/updatevalue(_:forkey:)">updateValue</a></p>
<h3 id="비슷한단어">비슷한단어</h3>
<ul>
<li>[[String]] 으로 문자 하나하나 받게 구성</li>
<li>array로 알파벳의 포함개수 </li>
</ul>
<hr />

<p><a href="https://www.acmicpc.net/problem/9935">https://www.acmicpc.net/problem/9935</a></p>
<p>클래스로 풀기</p>
<p><a href="https://solved.ac/class">https://solved.ac/class</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트위터 졸작-뜻하지 않은 코드 생사확인]]></title>
            <link>https://velog.io/@hyeonhee_bee/%EB%9C%BB%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@hyeonhee_bee/%EB%9C%BB%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%80-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sun, 12 Nov 2023 15:45:59 GMT</pubDate>
            <description><![CDATA[<p>리팩토링은 못했지만..그래도 나의 코드의 생사를 확인할 수있어 다행이다 
아맞다! 싶은 부분들이 많이 있었다. 코드 정말 지저분하다 히히...</p>
<p>에러가 발생했당</p>
<p>In a future version of the browser, cookies marked with SameSite=None; Secure and not Partitioned will be blocked in cross-site requests. This behavior protects user data from cross-site tracking.
Please refer to the article linked to learn more about preparing your site to avoid potential breakage.</p>
<p>쿠키가 생성되지 않아서 페이지가 넘어가질 않는 나의 졸작 </p>
<p>네트워킹 탭을 자세히 보니 url쪽에서 문제가 있는 것 같았다. 
폴더를 옮기면서 폴더구조가 바뀌어서 api에서 못받아오는 것이였음 </p>
<p>일단 코샌에서 잘 되는것은 확인했다. </p>
<p>밑 블로그들에서 알려준 samesite cookie 설정은 내 크롬브라우저에서 찾을 수 없어서 </p>
<p>(크롬 구글에서 chrome://flags/ 검색)
일단 cookie 관련 설정을 enable 진행해주었는데 이것도 잘 돌아가게 된데 영향이 있었던 건지 알수없다..나중에 한번 해보자(눈물)</p>
<p>혹시를 대비해 담이 추천해준데로 readme 에 잘 돌아가는 코샌박스 링크를 첨부했다. </p>
<p>그리고 혹혹시를 대비해 vercel에 배포해보는 중인데, 대충아는 자가 어거지로 배포해서 그런지 배포가 요상해서 트라이 해보는 중. 
next 빌드쪽에서 경고가 나서 참고했던 스택오버플로우 글..
경고문구는 다음과 같았다. 
You should not upload the <code>.next</code> directory.</p>
<p><a href="https://stackoverflow.com/questions/50947389/nextjs-cannot-find-a-valid-build-in-the-next-directory">https://stackoverflow.com/questions/50947389/nextjs-cannot-find-a-valid-build-in-the-next-directory</a></p>
<p>=&gt; 다양하게 시도해봤는데 원본 깃을 건드리지않고(이미 제출했음) 해당 경고를 해결하면서도 정상적으로 쿠키가 만들어지는 경우가 없어서, 굳이 vercel로 배포하지는 않기로 결정했다. </p>
<p>처음에 났던 오류와 비슷하게 url쪽에서 오류가 나는데 뭔가 ..흠 배포할때 .next를 지워보고 배포하거나 아니면 폴더구조를 변경해서 root를 바꾸는 등 다른방식으로 배포를 해봐야할듯하다. </p>
<p>대신 잘 동작하는 코샌영상을 git read me 에 추가해서 혹시나 모를 오류를 방지하기로 했다 </p>
<p>git 로 변경 후 issue를 이용해서 url을 구해서 영상을 올릴 수있는데 다음을 참고했다. </p>
<p><a href="https://pgmjun.tistory.com/47">https://pgmjun.tistory.com/47</a></p>
<p>참고링크
(원래 설명을 써놨는데..chrom flags에서 설정을 변경하면서 velog가 날라가서 그냥 적는다)</p>
<p><a href="https://darrengwon.tistory.com/647">https://darrengwon.tistory.com/647</a></p>
<p><a href="https://velog.io/@mjooon98/Next.js-vercel%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0">https://velog.io/@mjooon98/Next.js-vercel%EC%97%90-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</a></p>
<p><a href="https://velog.io/@codns1223/Etc-Vercel%EB%A1%9C-React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0">https://velog.io/@codns1223/Etc-Vercel%EB%A1%9C-React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift TIL(73)]]></title>
            <link>https://velog.io/@hyeonhee_bee/Swift-TIL73-nme5zg2p</link>
            <guid>https://velog.io/@hyeonhee_bee/Swift-TIL73-nme5zg2p</guid>
            <pubDate>Thu, 09 Nov 2023 09:02:54 GMT</pubDate>
            <description><![CDATA[<h3 id="faq-스터디">FAQ 스터디</h3>
<p>오늘은 가볍게 근황 + 서로 고민했던 부분 공유</p>
<p>마치가 열거형을 한번 더 짚어주어서 </p>
<ol>
<li>연관값과 원시값을 함께 쓸수없다는 것 </li>
<li>열거형을 그냥 RPS.rock 쓰는것과 RPS(rawvalue:3) 이렇게 쓰는것은 옵셔널에서 차이가 있다는 것 </li>
</ol>
<p>두가지를 리마인드 할수 있어서 준비해준 마치에게 고마웠다! 
그리고 쏭님과도 즐거운 근황토크를 진행했다 :) </p>
<p>나는 velog 에 정리한 내가 고민했던 부분 위주로 공유를 진행함</p>
<h3 id="알고리즘-스터디">알고리즘 스터디</h3>
<p>리버가 내 git 셋팅부터 homebrew 설치와 git 연동까지 거의 40분 이상을 시간을 내서 도와주었다 (미리 20분전에 만났음에도...오버함) 
너무 고맙고 볼때마다 대단한 리버쓰..
<a href="https://brew.sh">홈브류</a></p>
<p>그리고 다른 스터디원분들도 대단하다고 항상 생각한다(나 저때 뭐했누 싶은 헤헷)
감사하고 인류애 충전하는 스터디 시간 </p>
<h3 id="알고리즘-스터디준비">알고리즘 스터디준비</h3>
<p>알고리즘 문제 겨우 풀었(이걸 풀었다고 할수있을까...^_^) 다 </p>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/c94e1d78-32b4-4999-a265-a10df0bccbb9/image.png" alt="">
*<em>구밥이의 꿀팁 : 질문게시판을 잘 이용해보자! *</em></p>
<h4 id="참고자료-링크">참고자료 링크</h4>
<p><a href="https://glsman-111co.tistory.com/12">[iOS] Xcode DocC 사용법</a>
<a href="https://twih1203.medium.com/swift-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-swift-basic-%EC%B4%9D%EC%A0%95%EB%A6%AC-d86453bbeaa5">[Swift] 알고리즘에 필요한 Swift Basic 총정리</a>
<a href="https://developer.apple.com/documentation/swift/array/endindex">공식문서 endIndex</a>
<a href="https://velog.io/@leedool3003/iOS-swift-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-Tip-%EC%A0%95%EB%A6%AC">[iOS] swift 알고리즘, 코딩테스트에 필요한 tip 정리</a>
<a href="https://www.daleseo.com/python-enumerate/">파이썬의 enumerate() 내장 함수로 for 루프 돌리기</a></p>
<p><a href="https://developer.apple.com/documentation/swift/array/enumerated()">공식문서 enumerated()</a>
<a href="https://gudwns1243.tistory.com/31">[백준][파이썬]1138번: 한 줄로 서기</a>
<a href="https://stackoverflow.com/questions/42058478/type-int-does-not-conform-to-protocol-sequence">스택오버플로우-Type Int does not conform to protocol sequence</a></p>
<p>결국 마지막에 백준용 제출법을 몰라 찾아봐야 했던 </p>
<p><a href="https://blog.naver.com/PostView.naver?blogId=gustn3964&amp;logNo=222157822610&amp;parentCategoryNo=&amp;categoryNo=30&amp;viewDate=&amp;isShowPopularPosts=false&amp;from=postView">한 줄로 서기 - 백준 1138 - swift 
[출처] 한 줄로 서기 - 백준 1138 - swift|작성자 현무</a></p>
<p>모두모두 감사합니다 휴_휴</p>
<p>풀이를 마크다운으로 정리했다. </p>
<p>가볍게 일단 기록</p>
<h2 id="알쓰--season2---day1">알쓰  season2 - day1</h2>
<h3 id="문제--한줄로-서기-백준-1138번">문제 : 한줄로 서기 백준 1138번</h3>
<p>N명의 사람들은 매일 아침 한 줄로 선다. 이 사람들은 자리를 마음대로 서지 못하고 오민식의 지시대로 선다.</p>
<p>어느 날 사람들은 오민식이 사람들이 줄 서는 위치를 기록해 놓는다는 것을 알았다. 그리고 아침에 자기가 기록해 놓은 것과 사람들이 줄을 선 위치가 맞는지 확인한다.</p>
<p>사람들은 자기보다 큰 사람이 왼쪽에 몇 명 있었는지만을 기억한다. N명의 사람이 있고, 사람들의 키는 1부터 N까지 모두 다르다.</p>
<p>각 사람들이 기억하는 정보가 주어질 때, 줄을 어떻게 서야 하는지 출력하는 프로그램을 작성하시오.</p>
<hr>
<h3 id="입력-풀이">입력 풀이</h3>
<p>첫째 줄에 사람의 수 N이 주어진다. N은 10보다 작거나 같은 자연수이다.</p>
<p>둘째 줄에는 키가 1인 사람부터 차례대로 자기보다 키가 큰 사람이 왼쪽에 몇 명이 있었는지 주어진다.</p>
<p>i번째 수는 0보다 크거나 같고, N-i보다 작거나 같다. i는 0부터 시작한다.</p>
<hr>
<p><strong>예제</strong></p>
<pre><code class="language-swift">/*

 4
 2 1 1 0
 &gt;&gt;&gt;

 4 2 1 3

-----------
 5
 0 0 0 0 0
 &gt;&gt;&gt;

 1 2 3 4 5


------------ 

 6
 5 4 3 2 1 0
 &gt;&gt;&gt;

 6 5 4 3 2 1

  */
</code></pre>
<hr>
<h3 id="풀이">풀이</h3>
<p>예제 하나를 풀어보자 </p>
<pre><code class="language-swift">/*
 4
 2 1 1 0

 &gt;&gt;&gt;&gt;&gt;&gt;&gt;

 4 2 1 3
*/

첫번째 줄 의미 =&gt; 사람의 수 : 4명
두번째 줄 의미
=&gt;
0번째 / 키가 1인 사람 : 왼쪽에 키가 큰 사람이 2명 xx1x   0010
1번째 / 키가 2인 사람 : 왼쪽에 키가 큰 사람이 1명 x2xx.  0210
2번째 / 키가 3인 사람 : 왼쪽에 키가 큰 사람이 1명 xxx3   0213
3번째 / 키가 4인 사람 : 왼쪽에 키가 큰 사람이 0명 4xxx   4213

즉 4213 출력</code></pre>
<p>여기서 얻을 수 있는 힌트 : // 왼쪽에 몇명이 있는지 보고 그만큼 자리를 비우고 숫자를 채운다</p>
<pre><code class="language-swift">// 입력을 받아오는 코드 

let totalNumber = Int(readLine()!)!
let array = readLine()!.components(separatedBy: &quot; &quot;).map { Int($0)!} 

// resultArray에 0일 채운다. [ 0 0 0 0 ] 을 만든다. (스위프트는 조금..귀찮게 만들어야함)

var resultArray: [Int] = []
for _ in 0..&lt;totalNumber {
    resultArray.append(0)
}

// 반복문 + enumerated() 메서드를 이용해서 인덱스와 해당 인덱스의 요소를 튜플로 받는다. 

for (i, arrayNumber) in array.enumerated() {
    var count = 0
    /*i : 0 1 2 3   arrayNumber : 2 1 1 0

     왼쪽에 arrayNumber 명이 있을때
     1. 앞에 키 큰 사람을 그만큼 0이 비워져 있는지 확인하고, 비워져있다면 i+1을 채운다
     =&gt; 0 을 확인하고 0을 i+1 을 대체하는 행위는 resultArray 에서 일어나야하므로  
        루프를 한번 더 돌린다 */

    for (j, resultNumber)  resultArray.enumerated() {
         /*  j : 0 1 2 3   resultArrayNumber : 0 0 0 0

         2. 현재 갯수만큼(arrayNumber만큼) 0이 있는지 확인해야한다.  
            =&gt; 원하는 갯수의 0을 찾을 수 있을만큼 루프돌리기
            시도한 방법 1) j == arrayNumber 을 이용해서 조건이 성립할때까지 돌린다.
                      단 j == arrayNumber 을 이용해서 현재 갯수(arrayNumber) 만큼의  
                        0의 갯수를 확인하기 위한 루프를 돌리게 되면 
                        [0210] 인경우에서 빠져나감 (i 가 2 이고 키는 3 일때)
                        (i, arrayNumber) = (2,1) 왼쪽에 0은 한개여야 하는 루프에서  
                        동작하지 않는다
                        따라서 답이 [4 2 1 0] 으로 나오게 됨
                        따라서 원하는 횟수만큼 0을 확인하는 루프를 돌리려면
                        j == arrayNumber조건이 아닌 대체할 조건 하나가 더 필요하다
                        이 조건은 count 로 count 는 최종적으로는 2 1 1 0 즉 
                        arrayNumber와 같게된다. */

        // 0 이 있지만 아직 원하는 만큼의 arrayNumber 에 도달하지 못했다면

        if resultNumber == 0 &amp;&amp;  count &lt; arrayNumber {
            // 해당 문이 실행되면서 count 를 올린다
            count += 1   
        }

        /*count를 arrayNumber 만큼 올려놓았으므로 이때  
         (내가 원하는 시점,  
         즉 왼쪽의 0의 갯수인 arrayNumber 과 올려놓은 count가 일치하는 시점에서)
         해당 else if 문이 실행된다.
         이때 그 자리에 i+1 을 넣는다(왜냐면 키는 1부터 시작, i 는 0부터 시작이기 때문이다)*/

        else if  resultNumber == 0 &amp;&amp; count == arrayNumber {
            resultArray[j] = i+1
            break
           // 가장 가까운 for 문을 벗어난다 (바깥 for 문으로 돌아간다) 
        }
    }
}

// 다음과 같은 코드를 이용해서 하나하나 문자를 프린트 해줘야 백준에서 정답으로 인식가능 

resultArray.map({String($0)}).forEach{print($0, terminator : &quot; &quot; )}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift TIL(72) - swift 로 알고리즘 풀기 걸음마(guard let 문을 return없이 쓰기)]]></title>
            <link>https://velog.io/@hyeonhee_bee/Swift-TIL73</link>
            <guid>https://velog.io/@hyeonhee_bee/Swift-TIL73</guid>
            <pubDate>Wed, 08 Nov 2023 15:13:43 GMT</pubDate>
            <description><![CDATA[<p>오늘은 맥북은 겨우 켠 날..하루종일 작업물 정리하고 겨우 매일의 루틴을 하고나니 잘시간이다(기절)</p>
<p>내일 알고리즘(발등에 불떨어짐) 스터디를 위해 내가 선택한 한줄로 서기 문제를 보았다. </p>
<p>싸피를 준비했을 당시 배웠던 개념을 쓰는 문제인 것은 이해했는데, (구현문제를 골랐다) swfit 나 코드로 구현하기가 어려웠다. </p>
<p>이럴땐 답지를 보는게 상책이라(처음엔 다 이런거 아니겠는가..) 고민을 조금 해보다가 답지를 검색했다. </p>
<p>살짝 힌트를 얻어봤는데 일단 한번 돌려봐야겠다 싶어서 swift로 풀어보기로 결정했다. </p>
<p>우선 터미널에서 입력값을 받아오는 기초부터 시작해보자(난 알고리즘 유치원생이니까) </p>
<p>**
결론은 아래 링크에서 swift 로 백준풀기 글을 그냥 보시면 됩니다. **</p>
<h3 id="학습">학습</h3>
<p>readLine() 이라는 유용한 이미 만들어진 메서드를 사용하면되는데 주의할점은 이 메서드의 리턴이 옵셔널 타입이라는 것이다. </p>
<pre><code class="language-swift">func readLine(strippingNewline: Bool = true) -&gt; String?</code></pre>
<ul>
<li>보다시피 strippingNewline 파라미터는 true 로 디폴트 설정이 되어있어서 엔터를 신경쓰지 않는다면 따로 설정할필요가 없다고한다 
(공식문서 : 
strippingNewline
=&gt; If true, newline characters and character combinations are stripped from the result; otherwise, newline characters or character combinations are preserved. The default is true)
(true이면 결과에서 개행 문자 및 문자 조합이 제거되고, 그렇지 않으면 개행 문자 또는 문자 조합이 유지됩니다, 디폴트는true 입니다.)</li>
</ul>
<h4 id="참고-개행문자의-뜻">(참고) 개행문자의 뜻</h4>
<p>새줄 문자(newline)는, 
텍스트의 한 줄이 끝남을 표시하는 문자 또는 문자열이다. 개행 문자, 줄바꿈 문자(line break), EOL(end-of-line)과 같은 뜻이다. </p>
<p>출처 : [<a href="https://adnetwork.tistory.com/18%5D">https://adnetwork.tistory.com/18]</a>(개행 문자)</p>
<p>한마디로 엔터칠때 나타나는 문자를 표시해줄건지 말건지 인데, 디폴트인 true 로 두면(별다른 설정을 안하면) 제거한 값을 알려준다는 뜻이다. </p>
<p>*<em>여하튼 리턴값이 String? 으로 옵셔널인것이 여기서는 중요하다. *</em> </p>
<p>따라서 내가 참조한 블로그에서는 강제추출연산자 ! 를 사용해서 다음과 같이 옵셔널은 unwrapping 했다. </p>
<pre><code class="language-swift">let input = readLine()!</code></pre>
<p>하지만 궁금했다.</p>
<h3 id="guard-let-이나-if-let-으로-unwrapping-하면-어떨까">guard let 이나 if let 으로 unwrapping 하면 어떨까?</h3>
<p>(왜 이러는거냐 도대체) </p>
<p>*<em>참고로 혹시 끝까지 읽으실 분들이 있다면 그냥 강제추출연산자로 벗기시는게 가장 합리적일 것 같다는 결론에 이르렀다는 것을 안내드립니다. *</em>
(혹시 댓글로 관련 의견이 있다면 꼭 부탁드립니다..)</p>
<p>에러가 났다. </p>
<pre><code class="language-swift">guard let input = readLine() else { return } // 에러발생 

print(input)</code></pre>
<h4 id="에러">에러</h4>
<p>Return invalid outside of a func</p>
<p>이유를 스택오버플로우를 참조해서 얻을 수 있었다(공식문서에서도 얻을 수 있었을지도 모른다)</p>
<p><a href="https://stackoverflow.com/questions/47638413/how-to-exit-guard-outside-and-inside-a-function-swift">참고한 스택오버플로우</a></p>
<p>여기에 따르면 </p>
<blockquote>
<p>If the condition in your guard statement is not met, the else branch has to exit the current scope. return can only be used inside functions, as the error message shows, but return is not the only way to exit scope.you can use throw outside functions as well and if the guard statement is in a loop, you can also use break or continue.</p>
</blockquote>
<p>라고 한다. 
요약해보면<br>에러의 원인은 에러메시지에서 보듯 함수내에 있지 않은데 return 을 써서 이다. 
<strong>당연히!</strong>(바보같은 실수였네 아이코) 반환은 함수 내부에서만 사용할 수 있다.</p>
<h4 id="해결방법">해결방법</h4>
<p>글에 따르면
반환만이 범위를 종료하는 유일한 방법은 아니며, 함수 외부에서도 throw를 사용할 수 있고 가드 문이 루프에 있는 경우 break 또는 continue를 사용할 수도 있다고 한다. </p>
<p>나는 다른 것은 복잡해보여서 throw 를 써보았다. </p>
<pre><code class="language-swift">guard let input = readLine(strippingNewline: false) else { throw NSError() }
</code></pre>
<p>(break, continue 는 for 문 내에서 사용해야하고, return 은 함수를 따로 만들어 줘야한다)</p>
<h4 id="결과">결과</h4>
<pre><code class="language-swift">print(&quot;입력: &quot;, terminator: &quot;&quot;)
guard let input = readLine() else { throw NSError() }

print(input)
</code></pre>
<p><img src="https://velog.velcdn.com/images/hyeonhee_bee/post/acbe210c-cc4b-4e7a-a767-99a10eabf147/image.png" alt=""></p>
<p>참고로 개행문자(newline) 을 포함하면 (strippingNewline: false 설정) </p>
<pre><code class="language-swift">print(&quot;입력: &quot;, terminator: &quot;&quot;)
guard let input = readLine(strippingNewline: false) else { throw NSError() }

print(input)
</code></pre>
<p>다음과 같이 출력된다.
<img src="https://velog.velcdn.com/images/hyeonhee_bee/post/2bbcb9c5-7672-4ea2-80e5-4d0a3eafbf51/image.png" alt=""></p>
<p>참고로 사실 <strong>변수는 문자열 보간법으로 사용</strong>할 수 있으므로(차이를 느꼈던 적이 있는데 지금 정확히 기억이 안나서 슬프다) </p>
<p>*<em>굳이 guard let 을 쓸때의 코드는 다음의 코드가 더 정확하다고 생각한다 
*</em></p>
<pre><code class="language-swift">print(&quot;입력: &quot;, terminator: &quot;&quot;)
guard let input = readLine(strippingNewline: false) else { throw NSError() }

print(&quot;\(input)&quot;)
</code></pre>
<p>사실 제일 좋은 방법은 원 블로그처럼 강제추출연산자 ! 를 쓰는 것이다. 
NSError()를 굳이 해주는건 누가봐도 좋지 않을 듯 하다(뭔가 사이드 이펙트가 있을듯) </p>
<p>어차피 무조건적인 입력이 들어갈 예정이니까 모두 ! 를 사용하는게 어떨까.. </p>
<p>**
결론 : 아래 링크에서 swift 로 백준풀기 글을 그냥 보시면 됩니다. **</p>
<h4 id="우선-참조했던-자료들">우선 참조했던 자료들</h4>
<p><a href="https://www.acmicpc.net/problem/1138">백준 한줄로 서기 문제</a>
<a href="https://ryu-e.tistory.com/26">파이썬 백준 한줄로 서기 풀이</a>
<a href="https://please-amend.tistory.com/entry/%EB%B0%B1%EC%A4%80-%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8Swift%EB%A1%9C-%ED%92%80%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%EC%9E%85%EB%A0%A5-%EB%B0%9B%EB%8A%94-%EB%B2%95">스위프트로 백준풀기</a></p>
<p>나기님의 자료 
<a href="https://itholic.github.io/git-pull-request/#google_vignette">Github PR관련</a></p>
]]></description>
        </item>
    </channel>
</rss>