<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>BLEV 블레브</title>
    <link>https://blev.tistory.com/</link>
    <description>진짜 개발자가 되기 위한 기록과 일상

Swift, iOS, visionOS, Metal API</description>
    <language>ko</language>
    <pubDate>Sun, 7 Jun 2026 02:13:22 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>BLEV</managingEditor>
    <image>
      <title>BLEV 블레브</title>
      <url>https://tistory1.daumcdn.net/tistory/7911620/attach/d3a7e729cee34a7781d720b9056c37e3</url>
      <link>https://blev.tistory.com</link>
    </image>
    <item>
      <title>[iOS] MVC에서 MVI로, AI를 쓰기 전에 개발자가 미리 적용하자 (MVI + Clean Architecture with. SwiftUI</title>
      <link>https://blev.tistory.com/9</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w3NUM/dJMcahKoRZk/KkhSht7K4MmYJK6MN9yAOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w3NUM/dJMcahKoRZk/KkhSht7K4MmYJK6MN9yAOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w3NUM/dJMcahKoRZk/KkhSht7K4MmYJK6MN9yAOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw3NUM%2FdJMcahKoRZk%2FKkhSht7K4MmYJK6MN9yAOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;최근 AI가 정말 많이 발전했다.&lt;br /&gt;예전에는 카카오톡에서 '개발 도와주세요', '개발 고수님들 찾습니다'라는 오픈 채팅방을 쉽게 볼 수 있었는데,&lt;br /&gt;요새는 정말 보기가 힘들더라.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;AI를 쓰는건 정말 좋은것 같다. 귀찮은 코드를 대신 작성해주고, 새로운 코드나 기술을 적용할 때, 쉽게 설명해주니까.&lt;br /&gt;그런데 너무 AI 자체에 의존해서 생기는 문제들도 보여지고 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;특히 아키텍쳐나 패턴에 대한 지식이 하나도 없는 상태로 AI에게 코드를 맡겼다가, 유지보수가 불가능한 수준의 프로젝트가 생성되거나.&lt;br /&gt;반대로 AI가 특정 아키텍쳐나 패턴을 적용해주었는데, 개발자가 이해하지 못해서 평생을 AI에게만 맡겨야 하는 불상사가 생기고 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;물론 AI가 더, 더 발전해서 개발자가 아무런 설계를 하지 않아도 되는 세상이라면 상관 없겠지만,&lt;br /&gt;적어도 글을 쓰는 이 순간까지 개발자라면 유지보수가 좋은 코드를 작성하는 것이 중요하고, 그를 위한 설계도인 아키텍쳐/패턴에 대한 이해가 하나라도 있어야 하지 않겠냐는 의견이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;처음 만난 좋은 선임분들께 MVVM으로 시작해서, 지금은 MVI + Clean Architecture에 대한 이해가 조금이나마 되었다.&lt;br /&gt;상당히 어려웠고, 지금도 여러 패턴들을 왔다갔다 하다보면 헷갈릴 때가 가끔 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서. 템플릿을 만들고 공유하기로 했다.&lt;br /&gt;내가 쉽게 가져다 쓰기 위한 이유도 있고, 혹시나 직접 구글을 통해 정보를 찾아 헤메는 개발자들이 내가 만든 구조를 보고 MVI + Clean Architecture를 이해하는데 조금이나마 도움이 됐으면 좋겠다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: justify;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;*들어가기 전 주의*&lt;/i&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;1. iOS 앱을 기반으로 하고 Swift로 작성했습니다.&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;2. MVVM, 의존성 주입, protocol, 옵저버 패턴 등, 기본 지식이 없으면 이해가 어려울 수 있습니다.&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;3. 테스트 코드는 추가할 예정입니다.&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;4. 이런 상태 관리 패턴과 아키텍쳐는 대규모 프로젝트에서 유리합니다. 규모가 작거나 테스트 전용 앱에서는 오버엔지니어링을 유발할 수 있습니다. 클린 아키텍쳐와 MVI 패턴을 꼭 적용하는 것만이 정답은 아닙니다.&lt;/i&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;i&gt;궁금한 점이 있는 분은 댓글 또는 메일로 연락주세요.&lt;/i&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://github.com/BLEV-Woo/CleanMVI-iOS&quot; target=&quot;_self&quot;&gt;&lt;span&gt;GitHub - BLEV-Woo/CleanMVI-iOS&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;GitHub - BLEV-Woo/CleanMVI-iOS: iOS Clean Architecture + MVI + DI modular app template in SwiftUI&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;iOS Clean Architecture + MVI + DI modular app template in SwiftUI - BLEV-Woo/CleanMVI-iOS&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/BLEV-Woo/CleanMVI-iOS&quot; data-og-image=&quot;https://blog.kakaocdn.net/dna/dMBgST/dJMb8PGuq81/AAAAAAAAAAAAAAAAAAAAAPFweJGuQt0qUd0R3cNcAvlKJSauFGAF9EeebApvMNqb/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1774969199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=gPTUeDflMw5Wt7utxfApICqfdTI%3D&quot; data-og-url=&quot;https://github.com/BLEV-Woo/CleanMVI-iOS&quot;&gt;&lt;a href=&quot;https://github.com/BLEV-Woo/CleanMVI-iOS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/BLEV-Woo/CleanMVI-iOS&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://blog.kakaocdn.net/dna/dMBgST/dJMb8PGuq81/AAAAAAAAAAAAAAAAAAAAAPFweJGuQt0qUd0R3cNcAvlKJSauFGAF9EeebApvMNqb/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1774969199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=gPTUeDflMw5Wt7utxfApICqfdTI%3D');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - BLEV-Woo/CleanMVI-iOS: iOS Clean Architecture + MVI + DI modular app template in SwiftUI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;iOS Clean Architecture + MVI + DI modular app template in SwiftUI - BLEV-Woo/CleanMVI-iOS&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;자세한 내용은 REAMME.md에 작성했으니, 여기에는 간단하게만 작성해본다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;전체 구조: Clean Architecture + Multi-Module&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 단순히 파일 몇 개로 이루어진 패턴 예제가 아니다.&lt;br /&gt;실제 대규모 서비스에서도 적용할 수 있도록, 물리적인 모듈 분리를 전제로 설계했다.&lt;br /&gt;(물론 나는 아직 그 정도까지의 규모를 서비스해보진 못한것 같지만... 항상 염두하고, 공부하고, 적용하는게 개발자의 역할 아닐까)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;전체 레이어는 크게 5가지로 나눠진다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. App: 앱의 진입점. 모든 모듈을 조립하는 역할을 한다.&lt;br /&gt;2. Feature: UI와 화면 로직(MVI Store)이 위치한다.&lt;br /&gt;3. Domain: 비지니스 로직의 중심. 순수 Swift로만 작성되고 UseCase와 Entity가 포함된다.&lt;br /&gt;4. Data: 외부 API 호출이나 로컬 DB 접근을 담당하며, Domain의 protocol(Interface)을 실제로 구현한다.&lt;br /&gt;5. Core: 네트워크 클라이언트, 공통 아키텍쳐 베이스 등, 전역에서 사용하는 유틸리티 모듈&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MVI: 예측 가능한 단방향 데이터 흐름&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 템플릿의 핵심 UI 패턴은 MVI (Model-View-Intent)이다. SwiftUI의 선언적 UI와 잘 어울리는 패턴이다.&lt;br /&gt;이를 더 구조화하고 강제하는 것이 그 유명한 TCA (The Composable Architecture)이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;왜 TCA를 사용하지 않았냐고 묻는다면, 내가 알고 있는 TCA는 외부 라이브러리에 종속된다.&lt;br /&gt;TCA를 프로젝트에 import하고, 이미 정해진 규격에 따라야하기 때문에 유연한 대처가 불가능하다고 생각했다.&lt;br /&gt;MVI를 알아만둬도 TCA에 적응하기 쉬울 것이라는 생각도 든다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;MVI에는 State, Intent, Effect, ViewEffect가 있다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;왜 ViewEffect가 필요한가?&lt;/b&gt;&lt;br /&gt;일회성 이벤트를 일반적인 State에 넣으면, View가 다시 그려질 때 얼럿이 중복으로 뜨거나 상태를 매번 수동으로 초기화 해야 한다. 이를 해결하기 위해 ViewEffect를 활용한다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;부족하거나 완벽하지 않을 수 있지만, 누군가 내 코드를 보고 도움이 됐으면 좋겠다.&lt;/p&gt;</description>
      <category>개발/iOS</category>
      <category>Clean Architecture</category>
      <category>Di</category>
      <category>ios</category>
      <category>MVI</category>
      <category>MVVM</category>
      <category>sample</category>
      <category>Swift</category>
      <category>SwiftUI</category>
      <category>template</category>
      <category>UDF</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/9</guid>
      <comments>https://blev.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 11 Mar 2026 10:24:40 +0900</pubDate>
    </item>
    <item>
      <title>[개발] 모여바라 (Moyobara) 개발</title>
      <link>https://blev.tistory.com/8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 목표 중 하나였던, 앱 출시가 빠르게 끝나서 되돌아볼겸 글을 오랜만에 쓰게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2025년은 20살을 넘긴 이후로 정말 시간이 빨리 지나간 해였다. 안 좋은 의미로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 사이드 프로젝트를 진행했는지, 어떤 기술 스택을 썻는지, 결과는 어떤지 등등 &lt;b&gt;정리할 목적&lt;/b&gt;으로 써보려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;외근 시작 (2025년 8월 ~ 12월)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 업무로 외근을 나가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨리 끝내고 복귀해야지 생각하긴 했었는데, 생각보다 쉬운 일이 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 양이 많았던게 문제가 아니라. 해도 해도 원점으로 돌아오는 말도 안 되는 업무였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 정말 힘들었다. 다 끝냈다고 생각하고 다음 날 보면 다시 원점으로 돌아와 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 때문에 정확히 무슨 일인지 여기에 기록하진 못하지만, 같은 짓을 매일 매일 매일 매일 반복하고 있었고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 25년 11월에는 모든 작업이 마무리되고 복귀할 것이라는 희망을 가지고 있었지만, 어림도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 다이어트를 했어야 했나 싶은데, 오히려 스트레스 때문에 다들 엄청 먹고 증량만 했댄다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 외근은 12월까지 진행이 확정이 되었고. 제발 마무리라도 잘 되길 바라는 마음으로 다들 열심히 시간을 보냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면서 정신적으로 많이 무너졌던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 업무가 나한테, 개발적으로 도움이 되면 모를까. 진짜 단순히 노가다 뛰는 것 같은 느낌이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몸으로 떼우고, 같이 나간 직원들과 말싸움하고 신경질적이고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 내가 이러려고 iOS 개발을 배워놨나, 괜히 뭐 하나 할 줄 안다고 잡힌건가 싶기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 지나고 프로젝트도 기대 이상으로 잘 끝나서 이제서야 웃을 수 있는데, 회사 다니면서 제일 힘들었던 일 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 하루 이틀씩 사무실로 정리를 위해서 복귀를 했을 때, 오랜만에 잡아보는 Xcode와 Swift 코드는 진짜 손에 안 익더라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집에 오면 공부를 따로 하지 그래? 싶은데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루종일 몸으로 일하다가 집에 돌아오면 의자에 앉기는 커녕,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트레스를 해소한답시도 엄청 먹고 눕고 반복이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 중순이 되었을 때, 누군가 물어본 코드를 설명하려고 보니, 진짜 설명을 못 하겠더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외근을 나가기 전까지는 주마다 스터디를 하고 머리가 정말 잘 굴러갔었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 5개월을 안 하니까 진짜 다 잊고 있었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 충격을 많이 받았다. 그래서 언젠가 필요한 포트폴리오용 앱도 만들고, 공부도 다시하고, 개인 앱으로 앱스토어 배포도 할겸&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;개발 시작 (2025년 12월 ~ 2026년 2월)&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PJh7r/dJMcacWvjoN/HT0wMzS68D580QqDknPOFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PJh7r/dJMcacWvjoN/HT0wMzS68D580QqDknPOFk/img.png&quot; data-alt=&quot;12월 3일이 시작이었구나&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PJh7r/dJMcacWvjoN/HT0wMzS68D580QqDknPOFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPJh7r%2FdJMcacWvjoN%2FHT0wMzS68D580QqDknPOFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1318&quot; height=&quot;160&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;12월 3일이 시작이었구나&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 INTP다. INTP는 논리적인 토론을 하는걸 좋아한다(?) 라고 한 선임님이 그러더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 토론하는 앱이 있다면 (또는 치고박는 앱이 있다면) 사람들한테 인기가 많을 수도 있다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 앱을 만든다면 사용하게 될 기술 스택도 나쁘지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅, 게시판 앱이라면 가장 기본적인 것이라 생각했고, 로그인, 개인정보처리, API, DB 모든 것을 적용하기 완벽했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 생각이 들었던 시간에 바로 생성부터 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 정한 이름은 카피챗.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인기 많은 동물인 카피바라와 커피챗을 합쳐서, 자유로운 주제로 자유로운 대화를 할 수 있는 앱을 만들고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간으로 넘어가면서 좀 딱딱하다는 의견도 있고, 별로 재미없는 이름 같아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카피바라의 정체성을 유지하면서 말장난을 치는듯한 모여바라로 바꾸게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로젝트 구조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 프로젝트 구조를 정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배울 때도 그랬고, 알고 나서도 느끼는 거지만, 처음에 기능 구조던지, 코드 구조던지, 파일 구조던지 제대로 안 해놓으면 피를 꼭 본다는 생각을 먼저 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공부를 하는거니까 새로운걸 해볼까? 싶었는데, 그냥 원래 하던거나 잘하자고 생각했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;괜히 어렵고 새로운거 도전하다가 포기할 가능성도 있었고, 지금 당장 급한건 앱을 하나 완성해서 내는거지, 모험을 할 필요는 없다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 논리적 구조 - Clean Architecture&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 할 줄 아는게 이거밖에 없기도 하고, 가장 일반적이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 항상 하던거라 적용하기도 쉬웠고, 참고할 자료도 많았기 때문에, 바로 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UseCase, Repository, DataSource를 만드는건 그리 어렵지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 상태 관리 - MVVM + MVI (TCA)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 내가 알고 있었던건 MVVM 밖에 없었는데, 개발하면서 변형되고 적용된걸 나중에 공부해보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 하고 있었던건 MVVM + MVI + TCA 특성이 적절히 조합된 극강의 효율 세팅이었더라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 프로젝트 파일 구조 - Micro Architecture&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 표현이 맞는지는 모르겠는데, Xcode에서 Feature, Domain, Data + Shared, DesignSystem 등의 모듈을 만들어서 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;백엔드&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Firebase&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 개발자 없이 혼자 진행하는 프로젝트이니, 쉽게 구성하면서 내가 원하는 기능을 모두 구현할 수 있는데 딱 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 앱이기 때문에 회원관리가 필수인데, 이를 위해 소셜 로그인도 쉽게 구현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase에서도 주로 사용한 기능은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Authentication&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플 로그인을 구현하기 위해서 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Firebase Database&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 DB를 위해 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 Listener를 제공하기 때문에, 채팅 기능을 소켓 없이 구현하는데 도움을 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Storage&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 쓸 일이 없었는데, 개발 막판에 사용을 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 목적은 언어 필터링.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어 필터링을 위해 필요한 텍스트 데이터를 파일로 관리를 하고, 이를 저장하기 위해 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;디자인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 사실 제일 막막했다. AI의 도움을 받으면 된다고는 하는데, 생각보다 잘 만들어주지 못했다. 그런데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vFiqN/dJMcahcrU4x/zafGhDI06VKwTk7wvknWkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vFiqN/dJMcahcrU4x/zafGhDI06VKwTk7wvknWkk/img.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot; data-is-animation=&quot;false&quot; data-filename=&quot;Moyobara.png&quot; width=&quot;100&quot; style=&quot;width: 51.2048%; margin-right: 10px;&quot; data-widthpercent=&quot;51.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vFiqN/dJMcahcrU4x/zafGhDI06VKwTk7wvknWkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvFiqN%2FdJMcahcrU4x%2FzafGhDI06VKwTk7wvknWkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XxzLi/dJMb99MeSkk/WzicELHA3JqKKw3LCT2oLk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XxzLi/dJMb99MeSkk/WzicELHA3JqKKw3LCT2oLk/tfile.svg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;860&quot; data-filename=&quot;capybara_baby.svg&quot; width=&quot;100&quot; style=&quot;width: 47.6324%;&quot; data-widthpercent=&quot;48.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XxzLi/dJMb99MeSkk/WzicELHA3JqKKw3LCT2oLk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXxzLi%2FdJMb99MeSkk%2FWzicELHA3JqKKw3LCT2oLk%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAAXN/dJMcaiJakcW/fInLgSKxgBMW3Hh2jnHY5k/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAAXN/dJMcaiJakcW/fInLgSKxgBMW3Hh2jnHY5k/tfile.svg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;860&quot; data-filename=&quot;capybara_daewang.svg&quot; width=&quot;100&quot; style=&quot;width: 37.8324%; margin-right: 10px; margin-top: 10px;&quot; data-widthpercent=&quot;38.28&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAAXN/dJMcaiJakcW/fInLgSKxgBMW3Hh2jnHY5k/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAAXN%2FdJMcaiJakcW%2FfInLgSKxgBMW3Hh2jnHY5k%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BBm0e/dJMcabpNWee/X9Rmfhob9zGzeVRbTmSSNK/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BBm0e/dJMcabpNWee/X9Rmfhob9zGzeVRbTmSSNK/tfile.svg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot; data-filename=&quot;capybara_posting_phone.svg&quot; style=&quot;width: 61.0048%; margin-top: 10px;&quot; data-widthpercent=&quot;61.72&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BBm0e/dJMcabpNWee/X9Rmfhob9zGzeVRbTmSSNK/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBBm0e%2FdJMcabpNWee%2FX9Rmfhob9zGzeVRbTmSSNK%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인의 GPT가 뛰어나다고 맡겨만 달라던 동료가 있었는데, 덕분에 문제가 없어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외의 UI는 다른 앱들도 참고하고, 머리속으로 해보고 싶었던 UI/UX를 즉석으로 적용하면서 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GnlA5/dJMcaih7U1B/Bogq07LcxZ3EMnhcum4m40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GnlA5/dJMcaih7U1B/Bogq07LcxZ3EMnhcum4m40/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot; data-filename=&quot;IMG_5857.PNG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GnlA5/dJMcaih7U1B/Bogq07LcxZ3EMnhcum4m40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGnlA5%2FdJMcaih7U1B%2FBogq07LcxZ3EMnhcum4m40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GqFTp/dJMcab4ndJd/8B6sYAgn9I4Ul0EpgYoh2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GqFTp/dJMcab4ndJd/8B6sYAgn9I4Ul0EpgYoh2k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot; data-filename=&quot;IMG_5858.PNG&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GqFTp/dJMcab4ndJd/8B6sYAgn9I4Ul0EpgYoh2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGqFTp%2FdJMcab4ndJd%2F8B6sYAgn9I4Ul0EpgYoh2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/98wDu/dJMcagxPGeQ/9Me1IrYXqPP3PANpKBkMY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/98wDu/dJMcagxPGeQ/9Me1IrYXqPP3PANpKBkMY0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;2868&quot; data-filename=&quot;IMG_5860.PNG&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/98wDu/dJMcagxPGeQ/9Me1IrYXqPP3PANpKBkMY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F98wDu%2FdJMcagxPGeQ%2F9Me1IrYXqPP3PANpKBkMY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1320&quot; height=&quot;2868&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;귀여운 주제와 채팅들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 로그인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Firebase Authentication로 Apple 로그인만 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에 설명되어 있는대로 따라하기만 하면 큰 어려움없이 30분 내로 구현할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 내 주제 리스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 주제 리스트에는 4가지 채팅 리스트를 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1). 전체 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2). 내가 '좋아요'한 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3). 내가 '참여'한 리스트 (채팅을 친)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4). 내가 '생성'한 리스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 데이터는 모두, 회원가입/로그인에서 생성되는 데이터베이스에 함께 보관된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저별로 가지고 있기 때문에, 주제 ID 자체는 바로 가져올 수 있는데, 상세 정보를 위해서 1 + n개의 읽기를 해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 주제 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 주제 검색, 주제 생성, 주제 삭제, 주제 신고, 주제 숨기기 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주제라는 틀에서 할 수 있는 모든 동작들이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 유저 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주제 관리와 비슷하게, 유저 차단, 유저 글 숨기기, 유저 신고 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 주제 추천&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 주제들을 검토해서, 최근에 생성된 주제, 좋아요 많은 주제 등. 추천 시스템을 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 채팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 채팅이 들어간다, 각 주제별로 채팅을 쓸 수 있고, 주제에 들어간 시점에 리스너에 연결되어 모든 채팅이 실시간 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 기타 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1). 로그아웃, 회원탈퇴는 로그인 기능이 있으면 필수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2). Firebase Database에 version 정보 값도 저장해서 버전 검사 기능도 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3). 개인정보처리방침, 서비스 이용약관, 운영정책도 추가했다. 이것들은 slashpage라는 서비스를 활용해 WebView로 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4). 버전정보, 오픈소스 라이브러리 같은 정보도 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략적인 주요 기능들은 위와 같은데, 개발 기간은 약 3개월이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 업무 시간에는 하지 않고, 퇴근하고 시간 남을 때 하게 되어 예상보다 시간이 더 걸린것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말이나 공휴일에는 거의 시간을 갈아 넣었다. (설날 연휴에도 계속 했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;심사&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사하는데 작성해야 할 문구들은 AI의 도움을 많이 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 이미지는 Figma의 공개된 템플릿을 활용했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvzeQ/dJMb99ZPfjf/NWhd7VDAMyqX0NCw8aGw20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvzeQ/dJMb99ZPfjf/NWhd7VDAMyqX0NCw8aGw20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvzeQ/dJMb99ZPfjf/NWhd7VDAMyqX0NCw8aGw20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVvzeQ%2FdJMb99ZPfjf%2FNWhd7VDAMyqX0NCw8aGw20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1336&quot; height=&quot;796&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비하는건 빠르고 쉽게 했는데, 리젝을 한 번 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ8Gqt/dJMcabXB8ax/SpELxx3525mx6xLzMekQt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ8Gqt/dJMcabXB8ax/SpELxx3525mx6xLzMekQt0/img.png&quot; data-alt=&quot;Guideline 1.2 - Safety - User-Generated Content&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ8Gqt/dJMcabXB8ax/SpELxx3525mx6xLzMekQt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ8Gqt%2FdJMcabXB8ax%2FSpELxx3525mx6xLzMekQt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;712&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;1818&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Guideline 1.2 - Safety - User-Generated Content&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사유는 &lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;Guideline 1.2 - Safety - User-Generated Content&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;사용자 생성 콘텐츠 (주제와 채팅)이 있는데, 그를 위한 예방 조치가 완료되지 않았다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;저날 아침에 리젝을 알게 되었고, 회사에서 점심 시간이랑 퇴근후에 진짜 개발만 계속 해서 부족한 점을 채워서 다시 심사를 요청했고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: start;&quot;&gt;결국 심사를 통과했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니 근데, 심사를 4일이나 안 해줘서 결국 문의 메일 따로 넣고 나니까 그날 밤에 3분만에 통과시켜주더라..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;slashpage 서비스를 이용해 만든 웹사이트와 AppStore 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://slashpage.com/moyobara&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://slashpage.com/moyobara&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1772451676000&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Main - 모여바라 (Moyobara)&quot; data-og-description=&quot;모여바라 앱에 대한 정보를 제공합니다.&quot; data-og-host=&quot;slashpage.com&quot; data-og-source-url=&quot;https://slashpage.com/moyobara&quot; data-og-url=&quot;https://slashpage.com/moyobara&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c4aSx5/dJMb8QL9t6f/KpuetF2rAXpddCbyNf9nW0/img.jpg?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/N6WQl/dJMb8ZvyX37/NWK2Z2xDrnl5BjiqJXL6fk/img.jpg?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/gPbZk/dJMb8QejIvs/rrXbKnOzHczz4OOp3AHwKk/img.jpg?width=1242&amp;amp;height=2688&amp;amp;face=0_0_1242_2688&quot;&gt;&lt;a href=&quot;https://slashpage.com/moyobara&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://slashpage.com/moyobara&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c4aSx5/dJMb8QL9t6f/KpuetF2rAXpddCbyNf9nW0/img.jpg?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/N6WQl/dJMb8ZvyX37/NWK2Z2xDrnl5BjiqJXL6fk/img.jpg?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024,https://scrap.kakaocdn.net/dn/gPbZk/dJMb8QejIvs/rrXbKnOzHczz4OOp3AHwKk/img.jpg?width=1242&amp;amp;height=2688&amp;amp;face=0_0_1242_2688');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Main - 모여바라 (Moyobara)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;모여바라 앱에 대한 정보를 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;slashpage.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/id6757860890&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://apps.apple.com/kr/app/id6757860890&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772451730107&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;모여바라 앱 - App Store&quot; data-og-description=&quot;App&amp;nbsp;Store에서 YEONGHAK WOO의 모여바라 앱을 다운로드하십시오. 스크린샷, 평가 및 리뷰, 사용자 팁 및 모여바라 앱과 비슷한 다른 앱을 볼 수 있습니다.&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/id6757860890&quot; data-og-url=&quot;https://apps.apple.com/kr/app/%EB%AA%A8%EC%97%AC%EB%B0%94%EB%9D%BC/id6757860890&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yB4Rw/dJMb83kqrkk/qq3LAta0fdRIcmI7hMk8bk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dic7mJ/dJMb86OZkvZ/6I48VEBYbTxQ7LgSpdx3r0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/id6757860890&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apps.apple.com/kr/app/id6757860890&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yB4Rw/dJMb83kqrkk/qq3LAta0fdRIcmI7hMk8bk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dic7mJ/dJMb86OZkvZ/6I48VEBYbTxQ7LgSpdx3r0/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;모여바라 앱 - App Store&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;App&amp;nbsp;Store에서 YEONGHAK WOO의 모여바라 앱을 다운로드하십시오. 스크린샷, 평가 및 리뷰, 사용자 팁 및 모여바라 앱과 비슷한 다른 앱을 볼 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apps.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 크게 어려울것 없었다. 오히려 시간이 부족한게 제일 큰 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;걱정하던 것과 달리 개발은 잘 마무리 했고, 맥북을 만지니 코드 치던 습관이 잘 나왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 요새는 Gemini, Cursor 같은 AI도 잘 되어 있다보니 개발 자체에서 어려움은 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마음 먹는게 제일 어렵고 중요한 부분이었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 앱의 첫 번째 심사를 올리자마자, 새로운 앱 개발을 바로 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발이 점점 마무리 되면서 자신감이 붙었고, 아이디어가 막 떠올랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 쓰는 시점에는 이미 심사 요청까지 끝내놓은 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사가 잘 끝나면 모여바라의 버그 수정과 기능을 조금 더 붙여볼 생각이다.&lt;/p&gt;</description>
      <category>개발/잡동사니</category>
      <category>Firebase</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>개발</category>
      <category>개발자</category>
      <category>사이드프로젝트</category>
      <category>토이프로젝트</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/8</guid>
      <comments>https://blev.tistory.com/8#entry8comment</comments>
      <pubDate>Mon, 2 Mar 2026 20:49:18 +0900</pubDate>
    </item>
    <item>
      <title>[일상] 처음 쓰는데 마지막 글이 될 자전거 기록</title>
      <link>https://blev.tistory.com/7</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lkgko/btsNA48vKMs/8DJ0t3GWUg8qX1zo8pBNG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lkgko/btsNA48vKMs/8DJ0t3GWUg8qX1zo8pBNG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lkgko/btsNA48vKMs/8DJ0t3GWUg8qX1zo8pBNG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flkgko%2FbtsNA48vKMs%2F8DJ0t3GWUg8qX1zo8pBNG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;미리 말하지만 나는 자전거 찍먹만 했던 평범한 직장인이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;요새 자전거 타는데 맛이 들렸다.&lt;br&gt;&amp;nbsp;&lt;br&gt;원래 집에 로드 자전거가 있었는데, 동생꺼다.&lt;br&gt;그런데 동생이 자전거를 자주 안 타다보니 썩혀지고 있었고, 최근에 내가 운동이랍시고 그 자전거를 타기 시작했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;우리 집 근처에는 중랑천이 있고, 자전거 도로가 아주 잘 되어 있다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Aczek/btsNA1KIpzY/mf01ZVZgvy3kB94BVwgzF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Aczek/btsNA1KIpzY/mf01ZVZgvy3kB94BVwgzF1/img.png&quot; data-alt=&quot;어느 날씨 좋은 날&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Aczek/btsNA1KIpzY/mf01ZVZgvy3kB94BVwgzF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAczek%2FbtsNA1KIpzY%2Fmf01ZVZgvy3kB94BVwgzF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1440&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;어느 날씨 좋은 날&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하루에 한 번 자전거를 타고, 한 번 타면 보통 한 시간 정도 탄다.&lt;br&gt;속도는 한 20km 찍히니까, 거리도 20km 정도를 탄다는 뜻이다.&lt;br&gt;&amp;nbsp;&lt;br&gt;자전거 정보 찾아보고 하면 일반인들도 30km ~ 40km는 기본이던데.. 다들 괴물인가..&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xFQnt/btsNCbMv9Cy/DsX3zZSxojbRbJWzsvhtT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xFQnt/btsNCbMv9Cy/DsX3zZSxojbRbJWzsvhtT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xFQnt/btsNCbMv9Cy/DsX3zZSxojbRbJWzsvhtT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxFQnt%2FbtsNCbMv9Cy%2FDsX3zZSxojbRbJWzsvhtT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;810&quot; height=&quot;720&quot; data-origin-width=&quot;810&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;워치를 차고 달리면 자동으로 경로와 탄 거리 등이 기록되는데, 1시간 19.74km가 찍혔다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xZUus/btsNz9QkWcg/Uh3ZVrUB3cKCn0AvnbNZYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xZUus/btsNz9QkWcg/Uh3ZVrUB3cKCn0AvnbNZYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xZUus/btsNz9QkWcg/Uh3ZVrUB3cKCn0AvnbNZYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxZUus%2FbtsNz9QkWcg%2FUh3ZVrUB3cKCn0AvnbNZYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;431&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;워치를 차고 길게 탄건 처음이었는데, 바로 본인 신기록을 깨버렸다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 날이 4월 24일 목요일이었다.&lt;br&gt;다음 날인 25일 금요일은 조기 퇴근하는 날이었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 해서는 안 될 생각을 했다..&lt;br&gt;&amp;nbsp;&lt;br&gt;'집에 갔다가 나오기 귀찮은데 운동도 할겸 따릉이로 집까지 가볼까?'&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9wUz0/btsNCbZ75SQ/8pDFNTWRbk1ojDJ0HJaXa0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9wUz0/btsNCbZ75SQ/8pDFNTWRbk1ojDJ0HJaXa0/img.jpg&quot; data-alt=&quot;생각으로 멈춰!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9wUz0/btsNCbZ75SQ/8pDFNTWRbk1ojDJ0HJaXa0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9wUz0%2FbtsNCbZ75SQ%2F8pDFNTWRbk1ojDJ0HJaXa0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;386&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생각으로 멈춰!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;'얼마나 걸리나 한 번 봐볼까'&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FEKN0/btsNBE9jfYZ/hKnhvOcLWE3nwJRL0NSLhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FEKN0/btsNBE9jfYZ/hKnhvOcLWE3nwJRL0NSLhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FEKN0/btsNBE9jfYZ/hKnhvOcLWE3nwJRL0NSLhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFEKN0%2FbtsNBE9jfYZ%2FhKnhvOcLWE3nwJRL0NSLhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;518&quot; height=&quot;492&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxQbMC/btsNB9HWJyT/TdIq8vm2Gl7V0l5Zq5X701/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxQbMC/btsNB9HWJyT/TdIq8vm2Gl7V0l5Zq5X701/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxQbMC/btsNB9HWJyT/TdIq8vm2Gl7V0l5Zq5X701/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxQbMC%2FbtsNB9HWJyT%2FTdIq8vm2Gl7V0l5Zq5X701%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;114&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;'내가 한 시간에 20km를 타니까.... 오.. 괜찮은데?'&lt;br&gt;&amp;nbsp;&lt;br&gt;회사는 용산역쪽에 있고, 자전거 도로랑 가까우면서 따릉이를 빌릴 수 있는 동작역에서 출발하기로 했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그런데 정말 멍청했던게, 내가 타는 자전거는 로드 자전거고, 따릉이는..&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/84xjz/btsNBbGlUml/iatg7rTiwvCxggbecwMjek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/84xjz/btsNBbGlUml/iatg7rTiwvCxggbecwMjek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/84xjz/btsNBbGlUml/iatg7rTiwvCxggbecwMjek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F84xjz%2FbtsNBbGlUml%2Fiatg7rTiwvCxggbecwMjek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;821&quot; height=&quot;644&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서? 결론이?&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqkwD7/btsNCbMwc2b/0kBPM3gb8la8MXkoUNDimk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqkwD7/btsNCbMwc2b/0kBPM3gb8la8MXkoUNDimk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqkwD7/btsNCbMwc2b/0kBPM3gb8la8MXkoUNDimk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqkwD7%2FbtsNCbMwc2b%2F0kBPM3gb8la8MXkoUNDimk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;79&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;아 힘들어?&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blAUlo/btsNCvX66Bm/bKJwRKozg0CU8h45qYTDFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blAUlo/btsNCvX66Bm/bKJwRKozg0CU8h45qYTDFK/img.png&quot; data-alt=&quot;출발 하기 전에 GPT한테 물어봤어야지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blAUlo/btsNCvX66Bm/bKJwRKozg0CU8h45qYTDFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblAUlo%2FbtsNCvX66Bm%2FbKJwRKozg0CU8h45qYTDFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;546&quot; height=&quot;37&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출발 하기 전에 GPT한테 물어봤어야지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;...&lt;br&gt;진짜 힘들어 죽을뻔했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;아니 저 코스가 생각보다 평탄하지도 않았고,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o02wZ/btsNB982m0o/MnhexcvGo59TpVwskxcG2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o02wZ/btsNB982m0o/MnhexcvGo59TpVwskxcG2k/img.png&quot; data-alt=&quot;출처: 네이버 로드뷰&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o02wZ/btsNB982m0o/MnhexcvGo59TpVwskxcG2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo02wZ%2FbtsNB982m0o%2FMnhexcvGo59TpVwskxcG2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1017&quot; height=&quot;431&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: 네이버 로드뷰&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;특히 저 반포대교 오르막길이 죽는 줄 알았다...&lt;br&gt;&amp;nbsp;&lt;br&gt;다들 내려서 걸어 올라가길래 왜 다들 내려서 올라가지? 했는데, 따릉이는 기어 단수가 별로 없어서, 대신 내 다리가 기어가 되어서 열심히 돌아야만 했다.&lt;br&gt;심지어 저기는 강 바로 위에 있는 다리. 바람이 앞뒤양옆으로 엄청나게 불어내서 저항이 심하게 생겼다.&lt;br&gt;&amp;nbsp;&lt;br&gt;다 진짜 다 올라가서 허벅지 터지는 줄 알았다 흑흑&lt;br&gt;&amp;nbsp;&lt;br&gt;그래도 결국 집 앞까지 도착해서 반납은 했는데..&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/24W9R/btsNAhghtej/ocXluKUBC9wlqsIQZDdbNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/24W9R/btsNAhghtej/ocXluKUBC9wlqsIQZDdbNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/24W9R/btsNAhghtej/ocXluKUBC9wlqsIQZDdbNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F24W9R%2FbtsNAhghtej%2FocXluKUBC9wlqsIQZDdbNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;776&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;1시간 15분 걸렸다.&lt;br&gt;의외로 정상 시간에 도착했네?로 볼 수 있는데,&lt;br&gt;내가 아까 네이버 지도로 찍은 경로가 1시간 16분으로 &lt;b&gt;중랑천 자전거 도로에서 빠져나오는 곳까지만 찍은 것이다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;즉, 저기서 올라와서 다시 집 앞까지 가는데 더 타고 간거다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;...&lt;br&gt;&amp;nbsp;&lt;br&gt;심지어&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5hJcs/btsNAXhekFS/srxv5ahBmMwd0kOAl6BRek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5hJcs/btsNAXhekFS/srxv5ahBmMwd0kOAl6BRek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5hJcs/btsNAXhekFS/srxv5ahBmMwd0kOAl6BRek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5hJcs%2FbtsNAXhekFS%2Fsrxv5ahBmMwd0kOAl6BRek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;365&quot; height=&quot;342&quot; data-origin-width=&quot;365&quot; data-origin-height=&quot;342&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KOGXD/btsNA50EP7P/UY4eVQXC1jTR2ksOWLW9M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KOGXD/btsNA50EP7P/UY4eVQXC1jTR2ksOWLW9M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KOGXD/btsNA50EP7P/UY4eVQXC1jTR2ksOWLW9M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKOGXD%2FbtsNA50EP7P%2FUY4eVQXC1jTR2ksOWLW9M1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;53&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;1시간 지났다고, 추가 요금 과금된다는 문자까지 오네ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ&lt;br&gt;반납하니까 자동으로 600원이 더 결제되더라...&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ytYwl/btsNCbFNTOR/DJZzn70eAy7RtRoKREd6T1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ytYwl/btsNCbFNTOR/DJZzn70eAy7RtRoKREd6T1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ytYwl/btsNCbFNTOR/DJZzn70eAy7RtRoKREd6T1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FytYwl%2FbtsNCbFNTOR%2FDJZzn70eAy7RtRoKREd6T1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;418&quot; height=&quot;59&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그래도 운동은 제대로 했네...&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;다음부턴 그냥 집에 와서 타자 ^^&lt;/p&gt;</description>
      <category>일상</category>
      <category>개고생</category>
      <category>기록</category>
      <category>따릉이</category>
      <category>로드</category>
      <category>반포대교</category>
      <category>운동</category>
      <category>일상</category>
      <category>자전거</category>
      <category>한강</category>
      <category>한강 자전거</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/7</guid>
      <comments>https://blev.tistory.com/7#entry7comment</comments>
      <pubDate>Sat, 26 Apr 2025 23:32:17 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] iOS Toy 1: 스크랩 에디터, 캔버스 에디터, 제스처 조작하기</title>
      <link>https://blev.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbpZKV/btsNxB6VjVt/k89ZdIqypCLFqM4hpJPhQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbpZKV/btsNxB6VjVt/k89ZdIqypCLFqM4hpJPhQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbpZKV/btsNxB6VjVt/k89ZdIqypCLFqM4hpJPhQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbpZKV%2FbtsNxB6VjVt%2Fk89ZdIqypCLFqM4hpJPhQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024 관광데이터 활용 공모전에서 내가 메인으로 개발했던 기능 중 '스크랩 에디터'를 공유하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 앱에서는 REST API를 통해 캔버스 데이터를 불러오고 각 디바이스 사이즈에 맞게 재조정하고 배치하고..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성할 코드가 매우 많아서 힘들게 개발했었고, 매우 더럽게(?) 개발했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 간단하게 어떤 원리로 개발할 수 있는지, 이런 방법도 있다는 것을 공유하는 차원에서 글을 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;사용한 UI 프레임워크: SwiftUI&lt;br /&gt;주요 Skills: GeometryReader, 좌표, Gesture&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 'Canvas'를 사용하는 것도 있던데, 나는 ZStack을 이용해 구현할거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745658425511&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CanvasView.swift

import SwiftUI

struct CanvasView: View {
  
  // MARK: - ViewModel
  
  @StateObject private var viewModel: CanvasViewModel = CanvasViewModel()
  
  // MARK: - View
  
  var body: some View {
    VStack {
      HStack {
        // 버튼 들어갈 공간
      }
      
      
      GeometryReader { geo in
        HStack {
          ZStack {
            
          }
          .frame(width: viewModel.state.canvasSize.width, height: viewModel.state.canvasSize.height)
          .background(Color.white)
        }
        .frame(maxWidth: geo.size.width, maxHeight: geo.size.height)
        .onAppear {
          viewModel.send(action: .canvasDidAppear(size: geo.size))
        }
      }
    }
    .padding(20)
    .background(Color.gray)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745658443849&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CanvasViewModel.swift

import Foundation
import SwiftUI

final class CanvasViewModel: ObservableObject {
  
  // MARK: - Actions
  
  enum Action {
    case canvasDidAppear(size: CGSize)
  }
  
  // MARK: - States
  
  struct State {
    var canvasSize: CGSize = .zero
  }
  
  // MARK: - Properties
  
  @Published var state: State = State()
  
  // MARK: - Send
  
  func send(action: Action) {
    switch action {
    case .canvasDidAppear(let size):
      canvasDidAppear(with: size)
    }
  }
}

private extension CanvasViewModel {
  func canvasDidAppear(with size: CGSize) {
    setupCanvasSize(with: size)
  }
}

// MARK: - User Functions

private extension CanvasViewModel {
  func setupCanvasSize(with defaultSize: CGSize) {
    var newWidth = defaultSize.width
    var newHeight = defaultSize.width * 16 / 9
    
    if newHeight &amp;gt; defaultSize.height {
      newWidth = defaultSize.height * 9 / 16
      newHeight = defaultSize.height
    }
    
    state.canvasSize = CGSize(width: newWidth, height: newHeight)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;벌써 복잡해 보이네..&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GeometryReader 활용해서 Canvas의 크기를 제어할거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GeometryReader는 현재 가질 수 있는 최대 공간을 가지고, 그 공간의 크기를 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어온 크기를 ViewModel에서 9:16 비율로 잘라주는 작업을 한다. 그리고 진짜 캔버스가 될 ZStack의 크기로 잡아준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비율로 잘라주는 이유는 여러 기기에서 동일한 비율과 좌표값으로 제어하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 처리를 안 하고 단순 좌표값으로 스티커나 텍스트를 배치하려고 한다면, 기기마다 좌표값이 다르기 때문에 대참사가 난다. (경험담)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o4yCK/btsNATePIZk/xaGHhJ4yKFKxH7gCU0Z0UK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o4yCK/btsNATePIZk/xaGHhJ4yKFKxH7gCU0Z0UK/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot; data-filename=&quot;스크린샷&amp;amp;amp;#44; 2025-04-26 오후 6.11.53.jpeg&quot; width=&quot;200&quot; height=&quot;433&quot; style=&quot;width: 44.5876%; margin-right: 10px;&quot; data-widthpercent=&quot;45.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o4yCK/btsNATePIZk/xaGHhJ4yKFKxH7gCU0Z0UK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo4yCK%2FbtsNATePIZk%2FxaGHhJ4yKFKxH7gCU0Z0UK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X9ycf/btsNCu5UQ9J/xKmkpDmpsbYM7KVVJpsiK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X9ycf/btsNCu5UQ9J/xKmkpDmpsbYM7KVVJpsiK1/img.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1334&quot; data-is-animation=&quot;false&quot; width=&quot;280&quot; height=&quot;498&quot; style=&quot;width: 54.2497%;&quot; data-widthpercent=&quot;54.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X9ycf/btsNCu5UQ9J/xKmkpDmpsbYM7KVVJpsiK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX9ycf%2FbtsNCu5UQ9J%2FxKmkpDmpsbYM7KVVJpsiK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;1334&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽은 iPhone SE 3세대, 오른쪽은 내 iPhone 13 Pro이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 사이즈가 다르기 때문에 비율 계산을 해주고나니, 왼쪽은 위, 아래 공간이 남고 오른쪽은 꽉 차 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 캔버스를 완성했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아이템 하나를 올려보고 제어해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아이템을 정의하는 struct를 만들고, 그 배열을 ViewModel에 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이템도 이미지로 하나 추가해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745660393718&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CanvasItem.swift
...

class CanvasItem: Identifiable {
  var type: ItemType
  var position: CGPoint
  var size: CGSize
  var color: Color
  
  init(type: ItemType, position: CGPoint, size: CGSize, color: Color) {
    self.type = type
    self.position = position
    self.size = size
    self.color = color
  }
}

// CanvasViewModel.swift

...

// MARK: - States
  
  struct State {
    ...
    var canvasItems: [CanvasItem] = []
  }
  
...

private extension CanvasViewModel {
    ...
    func addSampleImage() {
    state.canvasItems.append(
      CanvasItem(
        type: .image(Image(systemName: &quot;gamecontroller.circle.fill&quot;)),
        position: .zero,
        size: CGSize(width: 50, height: 50),
        color: Color.blue
      )
    )
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다. 이 배열에 있는 아이템들이 ZStack 위에 올라갈 아이템들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 ZStack에 올리기 위해 View를 하나 만들어서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745661739090&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

struct CanvasItemView: View {
  
  // MARK: - Properties
  
  let item: CanvasItem
  
  // MARK: - Initializer
  
  init(item: CanvasItem) {
    self.item = item
  }
  
  // MARK: - View
  
  var body: some View {
    switch item.type {
    case .image(let image):
      image
        .resizable()
    case .text(let text):
      Text(text)
    }
  }
}

// CanvasView.swift
      GeometryReader { geo in
        HStack {
          ZStack {
            ForEach(viewModel.state.canvasItems.indices, id: \.self) { itemIndex in
              let item = viewModel.state.canvasItems[itemIndex]
              CanvasItemView(item: item)
                .frame(width: item.size.width, height: item.size.height)
                .position(item.position)
                .foregroundStyle(item.color)
            }
          }
          ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷, 2025-04-26 오후 7.03.51.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwurmB/btsNCpKwoQl/XB32D1wKFp3y7n6Y2kDqtK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwurmB/btsNCpKwoQl/XB32D1wKFp3y7n6Y2kDqtK/img.jpg&quot; data-alt=&quot;생기긴 했는데.. 상태가..?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwurmB/btsNCpKwoQl/XB32D1wKFp3y7n6Y2kDqtK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwurmB%2FbtsNCpKwoQl%2FXB32D1wKFp3y7n6Y2kDqtK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;280&quot; height=&quot;606&quot; data-filename=&quot;스크린샷, 2025-04-26 오후 7.03.51.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생기긴 했는데.. 상태가..?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치가 이상한데, 정상적이다. 해당 객체의 Center를 기준으로 ZStack의 0, 0 위치에 배치된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 살짝 조정을 해보면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745662088708&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let item = viewModel.state.canvasItems[itemIndex]
let itemPositionForCanvas = CGPoint(
  x: item.position.x + item.size.width / 2,
  y: item.position.y + item.size.height / 2
)
              
CanvasItemView(item: item)
  .frame(width: item.size.width, height: item.size.height)
  .position(itemPositionForCanvas)
  .foregroundStyle(item.color)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷, 2025-04-26 오후 7.08.15.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LmBYw/btsNBrvx23X/C86Yan4DM2voy3DjzXG620/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LmBYw/btsNBrvx23X/C86Yan4DM2voy3DjzXG620/img.jpg&quot; data-alt=&quot;편안해졌다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LmBYw/btsNBrvx23X/C86Yan4DM2voy3DjzXG620/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLmBYw%2FbtsNBrvx23X%2FC86Yan4DM2voy3DjzXG620%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;280&quot; height=&quot;606&quot; data-filename=&quot;스크린샷, 2025-04-26 오후 7.08.15.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;편안해졌다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Gesture만 남았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gesture는 각 Item마다 적용해줘야 하니 CanvasItemView에 적용해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745664122458&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.gesture(
  DragGesture()
    .onChanged { gesture in
      viewModel.send(action: .canvasItemDidDrag(index: itemIndex, gesture: gesture))
    }
    .onEnded { _ in
      viewModel.send(action: .canvasItemDragDidEnd)
    }
  )&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745664241869&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CanvasViewModel.swift

enum Action {
  ...
  case canvasItemDidDrag(index: Int, gesture: DragGesture.Value)
  case canvasItemDragDidEnd
}

private var positionDiffer: CGPoint?

func send(action: Action) {
  switch action {
    ...
    case .canvasItemDidDrag(let index, let gesture):
      canvasItemDidDrag(index: index, gesture: gesture)
    case .canvasItemDragDidEnd:
      canvasItemDragDidEnd()
  }
}

...

  func canvasItemDidDrag(index: Int, gesture: DragGesture.Value) {
    var updatedItems = state.canvasItems
    let selectedItem = updatedItems[index]
    
    if positionDiffer == nil {
      positionDiffer = CGPoint(
        x: selectedItem.position.x - gesture.startLocation.x,
        y: selectedItem.position.y - gesture.startLocation.y
      )
    }
    
    let newPosition = calculateNewPosition(gesture: gesture)
    selectedItem.position = newPosition
    updatedItems[index] = selectedItem
    
    state.canvasItems = updatedItems
  }
  
  func canvasItemDragDidEnd() {
    positionDiffer = nil
  }
  
  ...
  
  func calculateNewPosition(gesture: DragGesture.Value) -&amp;gt; CGPoint {
    var newPosition = CGPoint(
      x: gesture.location.x &amp;lt; state.canvasSize.width ? gesture.location.x : state.canvasSize.width,
      y: gesture.location.y &amp;lt; state.canvasSize.height ? gesture.location.y : state.canvasSize.height)
    
    newPosition = CGPoint(
      x: (gesture.location.x &amp;gt; 0 ? newPosition.x : 0) + positionDiffer!.x,
      y: (gesture.location.y &amp;gt; 0 ? newPosition.y : 0) + positionDiffer!.y
    )
    
    return newPosition
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gesture가 동작할 때마다 ViewModel에서 newPosition을 구하고, 전달된 index에 맞는 item의 Position을 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCDYhd/btsNA4m8sqf/csoaElVLSOjXCQSSUZpkl1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCDYhd/btsNA4m8sqf/csoaElVLSOjXCQSSUZpkl1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCDYhd/btsNA4m8sqf/csoaElVLSOjXCQSSUZpkl1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bCDYhd/btsNA4m8sqf/csoaElVLSOjXCQSSUZpkl1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;692&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 간단하게 Gesture를 적용하는 캔버스를 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기회가 된다면 화면 회전과 같은 더 복잡한 제스처와 제어 로직도 추가해봐야겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1745664572339&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dudgkr2014/iOSSample_1_CanvasEditor&quot; data-og-description=&quot;Contribute to dudgkr2014/iOSSample_1_CanvasEditor development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dudgkr2014/iOSSample_1_CanvasEditor&quot; data-og-url=&quot;https://github.com/dudgkr2014/iOSSample_1_CanvasEditor&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/h1TwE/hyYJuhz5KX/De7GqAMktR9RWjORerHvKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cZIEa2/hyYMVjYywm/aAnPNzXLVqVXzhVIwT8zG1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/dudgkr2014/iOSSample_1_CanvasEditor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dudgkr2014/iOSSample_1_CanvasEditor&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/h1TwE/hyYJuhz5KX/De7GqAMktR9RWjORerHvKk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cZIEa2/hyYMVjYywm/aAnPNzXLVqVXzhVIwT8zG1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dudgkr2014/iOSSample_1_CanvasEditor&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to dudgkr2014/iOSSample_1_CanvasEditor development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발/iOS</category>
      <category>ios</category>
      <category>Swift</category>
      <category>SwiftUI</category>
      <category>ZStack</category>
      <category>개발</category>
      <category>샘플</category>
      <category>캔버스</category>
      <category>토이프로젝트</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/6</guid>
      <comments>https://blev.tistory.com/6#entry6comment</comments>
      <pubDate>Sat, 26 Apr 2025 19:51:43 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 2024 관광데이터 활용 공모전: 트래버리 (2)</title>
      <link>https://blev.tistory.com/4</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;BLEV_misc.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7zqIx/btsNxiyj5bH/ZFPBB2XgOl1H3qaVEgkzE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7zqIx/btsNxiyj5bH/ZFPBB2XgOl1H3qaVEgkzE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7zqIx/btsNxiyj5bH/ZFPBB2XgOl1H3qaVEgkzE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7zqIx%2FbtsNxiyj5bH%2FZFPBB2XgOl1H3qaVEgkzE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;BLEV_misc.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEcAK1/btsNuHlZfbM/aL7dNa6hJcAfV6YgKUVLl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEcAK1/btsNuHlZfbM/aL7dNa6hJcAfV6YgKUVLl0/img.png&quot; data-alt=&quot;생각보다 하는 사람이 많네?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEcAK1/btsNuHlZfbM/aL7dNa6hJcAfV6YgKUVLl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEcAK1%2FbtsNuHlZfbM%2FaL7dNa6hJcAfV6YgKUVLl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;226&quot; height=&quot;347&quot; data-origin-width=&quot;226&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;생각보다 하는 사람이 많네?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제안서 통과 후, 5월 중순부터 본격적으로 프로젝트가 진행됐다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;트래버리: 여행의 모든 순간을 기록하는 습관&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;퇴근 후에 스터디룸을 잡아서 회의를 진행하고, 그에 필요한 기획, 디자인, 기술 검토 등이 진행됐다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데, 문제가 조금씩 생겼다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다들 처음 진행해 보는 프로젝트에서 다양한 기능과 기술을 넣어보기를 시도했고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발뿐 아니라 기획과 디자인도 새로운 시도를 하려고 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정말 좋은 기회였고, 해볼 만했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 양 조절에 실패했다...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;자체 API 서버 개발, Firebase, Apple 로그인, 카카오 로그인, 구글 로그인, 지도 커스텀, 스크랩 (캔버스)기능...&lt;br /&gt;회사에서는 하지 못했던 소통 방법, 새로운 디자인 방법, 앱에 적용하기 위한 세팅...&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기능 자체가 엄청나게 많았고, 기능을 개발하기 위한 기술 검토와 검증이 필요했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당연히 기술 검토에서 시간이 많이 지체되었고, 당연히 기획과 디자인도 미뤄졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발 완료는 10월 2일.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 7월 중순까지도 기획과 디자인이 전부 끝나지 않았고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 기획과 디자인을 진행하면서, 완료된 부분 부분을 바로 개발하는 방법으로 진행하게 되었다. 그리고, 처음 기획했던 기능에서 필수 기능을 제외하고, 어느 정도 타협을 보고 기능을 제거하기도 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러던 중, 꼭 필요한 기능이지만 엄청난 서버 측 개발 분량을 한 분이서&amp;nbsp;도저히 감당할 수 없었고, 결국 나의 동기인 서버 개발자 한 명을 추가 영입하게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쉽게 하는 게 아니구나..!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;API 통신, UI 그리기 같은 것들은 회사에서 항상 하는 것들이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당연히 문제없이 잘 해낼 수 있을 것 같았는데, 이번에는 달랐다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Google, 카카오 로그인은 그렇다 치고, Apple Login은 서버와 함께 개발해야 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터베이스로 쓰기로 한 Firebase는 제대로 사용하는 법도 숙지하지 못했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;회사에 함께 있을 때는 상관없었지만, 시간이 날 때 개발하는 사이드 프로젝트는 다른 인원의 도움이 필요할 때, 소통이 원활하게 진행되지도 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여러 난관에 부딪히면서 다시 걱정이 생기기 시작했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'정해진 시간 안에 끝낼 수 있을까?', '역시 괜히 시작했나?', '내 실력에 맞지 않는 과한 부분을 맡는 것 아닐까?'&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 열심히 해보기로 했다. 다른 팀원도 힘들지만 열심히 하는 것 같았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;입상을 하지 못하더라도 앱스토어에 출시는 해보고 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;10월이 점점 다가오면서 커져가는 불안감만큼, 새벽에 개발하는 시간도 늘어났다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정말 늦을 때는, 밤을 새워서 새벽 5시까지 개발을 하고, 1~2시간 자고 바로 출근하기도 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;많이 피곤했지만, 계획했던 기능을 하나씩 완성하면서 느끼는 뿌듯함으로 다음 기능을 쳐내기 시작했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크랩.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yz9Wu/btsNxT5TVHg/s7DiFr6q0CGf0PatmDEHtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yz9Wu/btsNxT5TVHg/s7DiFr6q0CGf0PatmDEHtK/img.png&quot; data-alt=&quot;이 스크랩 기능을 만드는게 너무 힘들었다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yz9Wu/btsNxT5TVHg/s7DiFr6q0CGf0PatmDEHtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyz9Wu%2FbtsNxT5TVHg%2Fs7DiFr6q0CGf0PatmDEHtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;292&quot; height=&quot;633&quot; data-filename=&quot;스크랩.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;2796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 스크랩 기능을 만드는게 너무 힘들었다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 다들 노력한 덕분에 개발은 완료했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월 3일까지 앱스토어에 게시를 완료했어야 했기 때문에, 빠르게 심사도 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKRHjt/btsNxlaLXUr/upBjIL1SgfjGI5WQbFal8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKRHjt/btsNxlaLXUr/upBjIL1SgfjGI5WQbFal8K/img.png&quot; data-alt=&quot;제발 한 번에 통과.. 팀원이 모두 기도했다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKRHjt/btsNxlaLXUr/upBjIL1SgfjGI5WQbFal8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKRHjt%2FbtsNxlaLXUr%2FupBjIL1SgfjGI5WQbFal8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;632&quot; height=&quot;365&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제발 한 번에 통과.. 팀원이 모두 기도했다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_7539.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjNpjH/btsNxhTJ0pn/jfhBRpkgpRqW7mBzmg2c01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjNpjH/btsNxhTJ0pn/jfhBRpkgpRqW7mBzmg2c01/img.png&quot; data-alt=&quot;그리고 기도가 성공했지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjNpjH/btsNxhTJ0pn/jfhBRpkgpRqW7mBzmg2c01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjNpjH%2FbtsNxhTJ0pn%2FjfhBRpkgpRqW7mBzmg2c01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;216&quot; data-filename=&quot;IMG_7539.png&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그리고 기도가 성공했지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 다행히도 심사는 한 번에 통과되었고, 앱스토어에 게시가 완료됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때까지도 좀 기대하고 있었다. '공모전을 진행하는 사람이 많긴 하지만, 입상할 수 있지 않을까?'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 결과를 기다렸지만...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;문자.jpg&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceI1hX/btsNvFg0Kdh/zZFSYqRSq0SDkYrLe0FdD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceI1hX/btsNvFg0Kdh/zZFSYqRSq0SDkYrLe0FdD1/img.jpg&quot; data-alt=&quot;다시 봐도 억장이 무너지네&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceI1hX/btsNvFg0Kdh/zZFSYqRSq0SDkYrLe0FdD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceI1hX%2FbtsNvFg0Kdh%2FzZFSYqRSq0SDkYrLe0FdD1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;666&quot; height=&quot;532&quot; data-filename=&quot;문자.jpg&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다시 봐도 억장이 무너지네&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 허무했지만 어쩔 수 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 자연스럽게 기능도 조절했고, 끝까지 완성하긴 했지만, 버그를 완벽하게 고칠 시간은 없었고, 테스트를 진행할 시간도 부족했다. 일단 출시까지는 성공했지만, 과정을 보면 입상 못한 게 당연했던 것 같기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나의 앱 출시, 그리고 공모전은 끝이 나게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후로는 가끔 App Store Connect에 들어가서 앱 통계를 보았고, 점점 잊어가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2월부터 10월까지, 거의 1년 동안 공모전과 앱 개발에 노력을 들였다. 내 인생에 첫 시도였고, 결국 수상작에 미선정되었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불가능해 보이는 기능을 개발해야 할 때 마음가짐을 긍정적으로 가질 수 있었고, 정말 불가능한 점에 대해서 그걸 설명하고 설득하는 법도 조금은 어설플지 모르겠지만 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 개발적인 부분이 아니더라도, 같이 프로젝트를 진행한 팀원들과도 친해진 데다가, 스터디를 아직까지도 꾸준히 진행하고 있으니, 그동안 힘들었던 것과 수상에 실패한 것을 좋은 경험이라고 생각한다면 얻어가는 것이 정말 많은 1년이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2747&quot; data-origin-height=&quot;2455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmojvU/btsNv6eqOUc/DjB5wiuMB82l5hgxpuxsfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmojvU/btsNv6eqOUc/DjB5wiuMB82l5hgxpuxsfK/img.png&quot; data-alt=&quot;마지막으로 확인하는 통계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmojvU/btsNv6eqOUc/DjB5wiuMB82l5hgxpuxsfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmojvU%2FbtsNv6eqOUc%2FDjB5wiuMB82l5hgxpuxsfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2747&quot; height=&quot;2455&quot; data-origin-width=&quot;2747&quot; data-origin-height=&quot;2455&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마지막으로 확인하는 통계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱은 곧 앱스토어에서 사라지겠지만, 남아 있을 코드와 프로젝트를 진행하면서 팀원들과 나눴던 대화는 나중에 또 다른 사이드 프로젝트를 진행하거나 시니어 개발자가 되어 있을 때, 큰 도움이 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure id=&quot;og_1745429347499&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;lrm;트래버리:여행의 모든 순간을 기록하는 습관&quot; data-og-description=&quot;&amp;lrm;Travel Diary, 트래버리 여행의 모든 순간을 간편하게 기록하고, 기록 콘텐츠로 오래 추억할 수 있도록 도와드릴게요. ■ 잊지말고 방문한 공간을 빠르게, 저장하기 - 여행 중 지금 이 순간을 남기&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; data-og-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cO8TZL/hyYIfcCn3D/jLXKlAYMuk76H7ZXPouzr1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kfR9C/hyYFycQvJJ/OHqgTCETc6TPKphY8nN0nK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cO8TZL/hyYIfcCn3D/jLXKlAYMuk76H7ZXPouzr1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kfR9C/hyYFycQvJJ/OHqgTCETc6TPKphY8nN0nK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;트래버리:여행의 모든 순간을 기록하는 습관&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;Travel Diary, 트래버리 여행의 모든 순간을 간편하게 기록하고, 기록 콘텐츠로 오래 추억할 수 있도록 도와드릴게요. ■ 잊지말고 방문한 공간을 빠르게, 저장하기 - 여행 중 지금 이 순간을 남기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apps.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1745429398326&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Travery Community - Travery&quot; data-og-description=&quot;차곡차곡 쌓는 여행 기록, Travery * 24년 10월 출시 예정&quot; data-og-host=&quot;slashpage.com&quot; data-og-source-url=&quot;https://slashpage.com/travery&quot; data-og-url=&quot;https://slashpage.com/travery&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/viHf0/hyYIaCnXL7/k6Q8Vs71NI9dNYyaVb3Es1/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/JsYLc/hyYJrku8c4/40ycWoKcFMFrcKkv3FHDnK/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/NY9gC/hyYFEqzY0U/kwqxRYK9FZ8Dj3j6DGC91K/img.jpg?width=1290&amp;amp;height=2796&amp;amp;face=0_0_1290_2796&quot;&gt;&lt;a href=&quot;https://slashpage.com/travery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://slashpage.com/travery&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/viHf0/hyYIaCnXL7/k6Q8Vs71NI9dNYyaVb3Es1/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/JsYLc/hyYJrku8c4/40ycWoKcFMFrcKkv3FHDnK/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/NY9gC/hyYFEqzY0U/kwqxRYK9FZ8Dj3j6DGC91K/img.jpg?width=1290&amp;amp;height=2796&amp;amp;face=0_0_1290_2796');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Travery Community - Travery&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;차곡차곡 쌓는 여행 기록, Travery * 24년 10월 출시 예정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;slashpage.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1745431625047&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[회고] 2024 관광데이터 활용 공모전: 트래버리 (1)&quot; data-og-description=&quot;블로그를 쓰게 되면서 이것저것 기록을 남기고 싶어졌다.특히 개발 관련 내용 중에서 생각 중이었는데, 2024년에 진행했던 프로젝트가 하나 생각났다. 2024 관광 데이터 활용 공모전. 그때 여행 기&quot; data-og-host=&quot;blev.tistory.com&quot; data-og-source-url=&quot;https://blev.tistory.com/5&quot; data-og-url=&quot;https://blev.tistory.com/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b1CXC4/hyYIdeNJhs/AEYGnaJvbU2EHlVEbIoJH0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/INZ5B/hyYFyRrWpK/Y0Nza5Nun4BR3OvTKymPpk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eQ1FyI/hyYFGhEX5q/ebKByG9wsp25EqKKeWU0G0/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024&quot;&gt;&lt;a href=&quot;https://blev.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blev.tistory.com/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b1CXC4/hyYIdeNJhs/AEYGnaJvbU2EHlVEbIoJH0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/INZ5B/hyYFyRrWpK/Y0Nza5Nun4BR3OvTKymPpk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eQ1FyI/hyYFGhEX5q/ebKByG9wsp25EqKKeWU0G0/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[회고] 2024 관광데이터 활용 공모전: 트래버리 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 쓰게 되면서 이것저것 기록을 남기고 싶어졌다.특히 개발 관련 내용 중에서 생각 중이었는데, 2024년에 진행했던 프로젝트가 하나 생각났다. 2024 관광 데이터 활용 공모전. 그때 여행 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/잡동사니</category>
      <category>ios</category>
      <category>개발</category>
      <category>공모전</category>
      <category>관광공사</category>
      <category>관광데이터</category>
      <category>사이드프로젝트</category>
      <category>토이프로젝트</category>
      <category>트래버리</category>
      <category>회고</category>
      <category>후기</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/4</guid>
      <comments>https://blev.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 24 Apr 2025 03:07:13 +0900</pubDate>
    </item>
    <item>
      <title>[회고] 2024 관광데이터 활용 공모전: 트래버리 (1)</title>
      <link>https://blev.tistory.com/5</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;BLEV_misc.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oCXqW/btsNw7X6Kzo/gQNLQIqhwvJhQxbYl9CDmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCXqW/btsNw7X6Kzo/gQNLQIqhwvJhQxbYl9CDmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCXqW/btsNw7X6Kzo/gQNLQIqhwvJhQxbYl9CDmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCXqW%2FbtsNw7X6Kzo%2FgQNLQIqhwvJhQxbYl9CDmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;BLEV_misc.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;블로그를 쓰게 되면서 이것저것 기록을 남기고 싶어졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 개발 관련 내용 중에서 생각 중이었는데, 2024년에 진행했던 프로젝트가 하나 생각났다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2024 관광 데이터 활용 공모전.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yt5WV/btsNwuMabEZ/AiYRJujtZ26C6kQtkiJzyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yt5WV/btsNwuMabEZ/AiYRJujtZ26C6kQtkiJzyk/img.png&quot; data-alt=&quot;공모전 홍보 포스터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yt5WV/btsNwuMabEZ/AiYRJujtZ26C6kQtkiJzyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYt5WV%2FbtsNwuMabEZ%2FAiYRJujtZ26C6kQtkiJzyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;802&quot; data-origin-width=&quot;1452&quot; data-origin-height=&quot;1778&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;공모전 홍보 포스터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그때 여행 기록 앱 '트래버리'를 개발했었고, 앱스토어에 출시까지 했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;출시를 2024년 10월쯤 했고, 약 6개월 정도 지났는데, 수익을 내려고 했던 것도 아니고, 입상한 앱도 아니어서 이제 슬슬 앱스토어 게시를 내릴 계획이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;성공한 앱은 아니지만, 첫 스토어 출시라는 의미가 있기에, 그리고 다양한 감정을 느꼈기 때문에 그 기억을 이렇게 남기려 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 개발자라면 서비스하는 앱 하나쯤은 있어야지 &lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;개발자라는 직업을 가지게 된 이후로 자주 생각했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히 나는 iOS '앱' 개발자. 앱 개발자라면 특히나 앱스토어에 내가 개발한 앱 하나쯤은 있어야 한다고 생각했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 앱스토어에 앱을 배포하기 위해서는 'Apple Developer Program'에 가입해야 했고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1년에 10만원이 조금 넘는 돈이 든다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당시 5만원 좀 넘어가는 물건이라면 지겨울 정도로 가성비를 따져가면서 이것저것 재보는 내 입장에서는 개발자로서의 커리어를 쌓는 것보다 돈이 중요했나 보다. 게다가 극강의 게으름과 귀찮음으로 평생을 살아왔던 나는 '앱 출시'는 또 상상만 그리다 끝나게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 큰 이유는 내 실력에 대한 의심이었다. 나는 지금도 그러지만 스스로를 개발자라고 하지 않으려고 한다. 개발을 많이 해본 것은 아니지만 세상에는 개발을 잘하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'진짜 개발자'&lt;/b&gt;들이 정말 많았고, iOS 개발을 이제 막 1년 정도 했던 나는 앱 출시 보다는 내 실력을 키우는 것이 우선이라는 핑계를 대며 회사 외에서의 개발을 미루고 있었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 회사에서 스터디를 하나 하고 있다. 우리 iOS 파트원 3명이서 진행한다. 우리 회사에서는 UIKit을 활용해 iOS 앱을 개발하고 있었다. 하지만, 변화하는 트렌드에 맞춰 SwiftUI 도입을 준비해야 했고, 이를 위해 스터디를 진행하게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SwiftUI 튜토리얼, 영화 API를 활용한 간단한 앱 하나 개발하기, 간단한 것들부터 하나씩 진행했고, SwiftUI를 조금 이해하고 나서는 SwiftUI 스터디가 아닌, 본격적인 개발 스터디가 되어있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러던 중, 우리 파트의 대장격인 선임님께서 관광데이터 활용 공모전에 대해 말씀하셨다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 관광데이터 활용 공모전. 우리 이거 해볼까요? &lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 게으름과 귀찮음으로 쩔어있는 나. 거기에 더해 아직은 서비스할 만한 앱을 개발할 수 없을 것이라는 생각에 망설여졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특히나 나는&amp;nbsp;&lt;b&gt;걱정을 너무 심하게 하는 사람이었다&lt;/b&gt;&lt;b&gt;.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'입상 못하면 어쩌지?', '중간에 포기하면 어쩌지?', '기획과 디자인은 누가하지?'&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시작하지도 않았는데 벌써부터 저 멀리 달려 나가서 넘어질 걱정을 하고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데, 다른 두 분은 개발 경험에 좋을 것이라고 생각했고, 기획과 디자인도 우리가 그냥 하면 된다는 마인드였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나도 동의하게 되었고, 2024년 공모전 모집이 시작되기 전, 우리는 스터디를 할 때마다 가끔씩 아이디어와 계획을 의논하기 시작했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 간단하게 시작했다. 3명이 기획부터 디자인, 개발까지 모두 하려니, 최대한 간소화하면서 핵심 기능을 개발하기로 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;907&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcL9RK/btsNw1cwGO9/FKbmCftOUZvEffofHfc0pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcL9RK/btsNw1cwGO9/FKbmCftOUZvEffofHfc0pK/img.png&quot; data-alt=&quot;당시, Github의 Discussions으로 정리했었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcL9RK/btsNw1cwGO9/FKbmCftOUZvEffofHfc0pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcL9RK%2FbtsNw1cwGO9%2FFKbmCftOUZvEffofHfc0pK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;627&quot; height=&quot;872&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;907&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;당시, Github의 Discussions으로 정리했었다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각자 메인이 될 것 같은 기능들에 대해 기술 검토와 구현 난이도 등을 조사하면서 준비를 하고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각보다 재미있고, 설레었다. &quot;Hello, World&quot;를 찍을 때만큼이나 두근거렸고, 처음에 했던 걱정은 기대가 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'입상하면 상금으로 뭐 하지?', '맥북을 새로 하나 살까?', '엄청난 경력이 커리어가 한 줄 생기겠다!'&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최종적으로 개발하기로 한 앱은 여행 기록 앱이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대한민국 지도 위에 내가 다녀온 여행지를 체크할 수 있는 기능을 우리의 차별화로 밀기로 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러던 3월 말. 공모전 제안 접수를 1주일 정도 앞두고 있을 때, 회사의 다른 분들도 프로젝트에 합류하게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 개발자 1명, 기획자 1명, 디자이너 1명.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;iOS 개발자 3명에서 그럴듯한 앱 개발 팀 하나가 생기게 되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다들 바쁜 일정을 소화하면서, 제안서를 작성하고 제출까지 성공적으로 끝냈다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 문제없이 제안서는 통과되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;figure id=&quot;og_1745431436621&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;lrm;트래버리:여행의 모든 순간을 기록하는 습관&quot; data-og-description=&quot;&amp;lrm;Travel Diary, 트래버리 여행의 모든 순간을 간편하게 기록하고, 기록 콘텐츠로 오래 추억할 수 있도록 도와드릴게요. ■ 잊지말고 방문한 공간을 빠르게, 저장하기 - 여행 중 지금 이 순간을 남기&quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; data-og-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cO8TZL/hyYIfcCn3D/jLXKlAYMuk76H7ZXPouzr1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kfR9C/hyYFycQvJJ/OHqgTCETc6TPKphY8nN0nK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apps.apple.com/kr/app/%ED%8A%B8%EB%9E%98%EB%B2%84%EB%A6%AC-%EC%97%AC%ED%96%89%EC%9D%98-%EB%AA%A8%EB%93%A0-%EC%88%9C%EA%B0%84%EC%9D%84-%EA%B8%B0%EB%A1%9D%ED%95%98%EB%8A%94-%EC%8A%B5%EA%B4%80/id6673890168&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cO8TZL/hyYIfcCn3D/jLXKlAYMuk76H7ZXPouzr1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kfR9C/hyYFycQvJJ/OHqgTCETc6TPKphY8nN0nK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;트래버리:여행의 모든 순간을 기록하는 습관&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;Travel Diary, 트래버리 여행의 모든 순간을 간편하게 기록하고, 기록 콘텐츠로 오래 추억할 수 있도록 도와드릴게요. ■ 잊지말고 방문한 공간을 빠르게, 저장하기 - 여행 중 지금 이 순간을 남기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apps.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1745431445397&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Travery Community - Travery&quot; data-og-description=&quot;차곡차곡 쌓는 여행 기록, Travery * 24년 10월 출시 예정&quot; data-og-host=&quot;slashpage.com&quot; data-og-source-url=&quot;https://slashpage.com/travery&quot; data-og-url=&quot;https://slashpage.com/travery&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/viHf0/hyYIaCnXL7/k6Q8Vs71NI9dNYyaVb3Es1/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/JsYLc/hyYJrku8c4/40ycWoKcFMFrcKkv3FHDnK/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/NY9gC/hyYFEqzY0U/kwqxRYK9FZ8Dj3j6DGC91K/img.jpg?width=1290&amp;amp;height=2796&amp;amp;face=0_0_1290_2796&quot;&gt;&lt;a href=&quot;https://slashpage.com/travery&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://slashpage.com/travery&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/viHf0/hyYIaCnXL7/k6Q8Vs71NI9dNYyaVb3Es1/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/JsYLc/hyYJrku8c4/40ycWoKcFMFrcKkv3FHDnK/img.jpg?width=1280&amp;amp;height=1280&amp;amp;face=0_0_1280_1280,https://scrap.kakaocdn.net/dn/NY9gC/hyYFEqzY0U/kwqxRYK9FZ8Dj3j6DGC91K/img.jpg?width=1290&amp;amp;height=2796&amp;amp;face=0_0_1290_2796');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Travery Community - Travery&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;차곡차곡 쌓는 여행 기록, Travery * 24년 10월 출시 예정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;slashpage.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1745431457963&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[회고] 2024 관광데이터 활용 공모전: 트래버리 (2)&quot; data-og-description=&quot;제안서 통과 후, 5월 중순부터 본격적으로 프로젝트가 진행됐다.트래버리: 여행의 모든 순간을 기록하는 습관 퇴근 후에 스터디룸을 잡아서 회의를 진행하고, 그에 필요한 기획, 디자인, 기술 검&quot; data-og-host=&quot;blev.tistory.com&quot; data-og-source-url=&quot;https://blev.tistory.com/4&quot; data-og-url=&quot;https://blev.tistory.com/4&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lFykH/hyYFB1HZ17/sYFqJtdnZrMQBQsFWjhxz1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/vDsAw/hyYH5uj5mU/VY4gEseBGg5IslcjcKjw00/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byDMxQ/hyYFzbJyc8/cW6ttkKQFpBRpfQK9uapfk/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024&quot;&gt;&lt;a href=&quot;https://blev.tistory.com/4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blev.tistory.com/4&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lFykH/hyYFB1HZ17/sYFqJtdnZrMQBQsFWjhxz1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/vDsAw/hyYH5uj5mU/VY4gEseBGg5IslcjcKjw00/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/byDMxQ/hyYFzbJyc8/cW6ttkKQFpBRpfQK9uapfk/img.png?width=1024&amp;amp;height=1024&amp;amp;face=0_0_1024_1024');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[회고] 2024 관광데이터 활용 공모전: 트래버리 (2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;제안서 통과 후, 5월 중순부터 본격적으로 프로젝트가 진행됐다.트래버리: 여행의 모든 순간을 기록하는 습관 퇴근 후에 스터디룸을 잡아서 회의를 진행하고, 그에 필요한 기획, 디자인, 기술 검&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blev.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>개발/잡동사니</category>
      <category>ios</category>
      <category>개발</category>
      <category>공모전</category>
      <category>관광공사</category>
      <category>관광데이터</category>
      <category>사이드프로젝트</category>
      <category>토이프로젝트</category>
      <category>트래버리</category>
      <category>회고</category>
      <category>후기</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/5</guid>
      <comments>https://blev.tistory.com/5#entry5comment</comments>
      <pubDate>Thu, 24 Apr 2025 03:06:26 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 익명 함수와 클로저</title>
      <link>https://blev.tistory.com/2</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYJna5/btsNuexvG5t/RkqFFCcor9dxukC1wIOtS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYJna5/btsNuexvG5t/RkqFFCcor9dxukC1wIOtS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYJna5/btsNuexvG5t/RkqFFCcor9dxukC1wIOtS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYJna5%2FbtsNuexvG5t%2FRkqFFCcor9dxukC1wIOtS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;모든 개발 언어에서 '익명 함수 (Anonymous Function)'라는 개념이 존재한다.&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;개발을 시작하고 약 1년은 Kotlin으로 Android를 개발했었는데,&lt;br /&gt;당시에는 익명함수, 람다를 전혀 이해하지 못했고, 너무 어려워했던 기억이 난다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;시간이 조금은 지난 지금, 람다뿐 아니라 어느 정도 개발에 익숙해진 시점에서 그때를 떠올려 보면,&lt;br /&gt;그렇게 힘들어했던 것도 이해가 되지만, 왜 그렇게 어렵게 느꼈었는지, 어렵게 공부했었는지 아쉬운 생각도 든다.&lt;br /&gt;아마 나보다 더 오래된 개발자들도 비슷한 감정을 느껴본 적 있지 않을까 싶다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그래서, 그때의 나를 위로하는 마음으로 익명 함수, 클로저에 대해 정리해보려 한다.&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;물론, 이거 말고도 힘들어 한게 한 둘이 아니긴 하다.. (지금도 힘든거 많ㅇ)&lt;/s&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;익명 함수? 클로저?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 함수란 말 그대로 &lt;b&gt;이름이 없는 함수&lt;/b&gt;이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;이름이 없는 함수 &lt;b&gt;'익명 함수'&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;대부분의 언어에서 익명 함수를 지원한다.&lt;br /&gt;Swift에서는 클로저(Closure), Kotlin에서는 람다(Lambda)로 표현한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여기서 중요한 점은, 클로저나 람다는 익명 함수를 표현하는 방식, 즉 구현 문법일 뿐이라는 것이다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Swift에서는 익명 함수를 표현하기 위해 클로저라는 방법을 사용한 것이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Swift에서는 익명 함수를 클로저 문법을 통해 표현한다.&lt;/b&gt;&lt;br /&gt;클로저는 중괄호 {}를 사용해서 코드 블록을 정의하는 것을 말한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;만약 다음과 같다면?&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;{
&amp;nbsp;&amp;nbsp;let x: Int = 3
&amp;nbsp;&amp;nbsp;let y: Int = 8
&amp;nbsp;&amp;nbsp;return x + y
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이것이 익명 함수고 클로저 문법으로 작성한 것이다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;하지만 이렇게 선언만 해서는 사용할 수 없다.&lt;br /&gt;함수는 결국 호출을 해서 사용을 해야 하기 때문에, 익명 함수도 호출할 수 있는 방법이 있어야 하고,&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;var add: () -&amp;gt; Int = {
&amp;nbsp;&amp;nbsp;let x: Int = 3
&amp;nbsp;&amp;nbsp;let y: Int = 8
&amp;nbsp;&amp;nbsp;return x + y
}

let sum = add() // 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위와 같이 변수에 클로저 참조를 담아 사용한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;'add'라는 이름이 있는 것 아닌가요?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;위 내용을 보고 바로 들었던 생각이다.&lt;br /&gt;'add'라는 이름이 있는 것 같은데 익명 함수란다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 부분이 헷갈릴 수 있지만, 핵심적인 내용이다.&lt;br /&gt;add는 단지 저장 공간일 뿐이고, 우리가 정의한 익명 함수(클로저)는 여전히 이름이 없다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;함수가 되기 위해서는 func 키워드와 함께 이름이 붙어야 한다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;swift&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;// 클로저
let addClosure: (Int, Int) -&amp;gt; Int = { x, y in
&amp;nbsp;&amp;nbsp;return x + y
}

// 일반 함수
func addFunction(_ x: Int, _ y: Int) -&amp;gt; Int {
&amp;nbsp;&amp;nbsp;return x + y
}

// 호출
let resultFromClosure = addClosure(5, 6)
let resultFromFunction = addFunction(5, 6)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;실제로 어떻게 쓰이는지는 다음 포스팅에서 해야겠다.&lt;br /&gt;일급 객체, 고차 함수 내용을 담게 되면 내용이 너무 많아질 것 같다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Swift</category>
      <category>closure</category>
      <category>ios</category>
      <category>Objective-C</category>
      <category>Swift</category>
      <category>개발</category>
      <category>람다</category>
      <category>익명 함수</category>
      <category>클로저</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/2</guid>
      <comments>https://blev.tistory.com/2#entry2comment</comments>
      <pubDate>Tue, 22 Apr 2025 21:11:03 +0900</pubDate>
    </item>
    <item>
      <title>[Metal] Metal Toy 1: 텍스처 색상 반전</title>
      <link>https://blev.tistory.com/1</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;BLEV_Metal.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UpuFO/btsNs7qN49m/s7krfkG8DTMra31y6ByiD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UpuFO/btsNs7qN49m/s7krfkG8DTMra31y6ByiD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UpuFO/btsNs7qN49m/s7krfkG8DTMra31y6ByiD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUpuFO%2FbtsNs7qN49m%2Fs7krfkG8DTMra31y6ByiD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-filename=&quot;BLEV_Metal.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;iOS 개발을 하면서 그래픽을 건드리게 된다면, Metal 이라는 단어를 듣게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;OpenGL, DirectX처럼 GPU를 직접 제어할 수 있는 API로, Apple 생태계(iOS, macOS 등)에 특화된 성능 최적화된 API이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런말을 사용할 때면 항상 아쉬운게 처음 접하는 입장에서는 저런식의 정의가 쉽게 와닿지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 항상 내가 이해할 수 있는 방법으로 이해하고, 나중에 다시 정의를 보면 잘 이해가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그래서 나는 Metal을 이렇게 이해하고 학습을 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;화면에 2D나 3D로 뭔가를 그리거나 연산을 할 때 직접 GPU를 활용할 수 있도록 하는 Apple 전용 그래픽 API&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사실 예제에 관련된 포스팅을 하기 전에 Metal에 대해서 먼저 자세히 알아봐야 하지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;스터디에서 발표할 내용을 앞으로 여기에 작성할거기 때문에, 이번 주 발표할 내용을 이렇게 먼저 작성하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;개인적으로 느끼기에 Metal은 상당히 어렵고 찾을 수 있는 한국어 자료도 많이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그렇기 때문에 간단한 예제와 함께 설명을 작성해두면 나중에 내가 다시 볼 수도 있고, 누군가에게 설명하기에 용이할 것 같았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;(사실 GPT와 함께라면 못할 것이 없지만)&lt;/s&gt;&lt;/span&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;&lt;s&gt;&lt;/s&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 예제는 카메라로 찍히는 이미지를 Metal을 통해 색을 반전시키는 예제다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;1319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhXYqm/btsNt1RMxY3/jq1dSh23DxOQ9dGHDcOJw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhXYqm/btsNt1RMxY3/jq1dSh23DxOQ9dGHDcOJw0/img.png&quot; data-alt=&quot;식욕이 떨어지는 딸기..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhXYqm/btsNt1RMxY3/jq1dSh23DxOQ9dGHDcOJw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhXYqm%2FbtsNt1RMxY3%2Fjq1dSh23DxOQ9dGHDcOJw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;594&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;1319&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;식욕이 떨어지는 딸기..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Metal에는 Render Pipeline과 Compute Pipeline이 있는데, 뭔가를 그리기 위한 &lt;b&gt;Render Pipeline&lt;/b&gt;을 설명하기에 아주 좋은 예제이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 순서를 설명하면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AVFoundation을 통해 Video Frame Output을 받아 저장한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;MTKView에서 저장된 Frame을 가져와 Metal 연산을 커밋한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;Metal Shader를 통해 Frame(Texture)에 대한 연산을 한다. (색 반전)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;연산된 결과가 MTKView에 그려진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 순서가 반복되면서 Camera 촬영한 세상이 색이 반전되어 화면에 연속적으로 그려진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용한 언어: Swift, C++ (Metal Shader)&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 Metal로 연산된 결과를 보여주기 위한 전용 View, MTKView를 만들어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;UIKit 기반이기 때문에 UIViewRepresentable를 사용해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745209697519&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import MetalKit
import SwiftUI

struct MetalVideoFilterView: UIViewRepresentable {
  
  func makeUIView(context: Context) -&amp;gt; MTKView {
    let mtkView = MTKView()
    return mtkView
  }
  
  func updateUIView(_ uiView: MTKView, context: Context) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745224543706&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SwiftUI

struct ContentView: View {
  var body: some View {
    MetalVideoFilterView()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태로 실행하면 아무것도 보이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MTKView에 그린 것이 없기 떄문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MTKView에 Metal과 관련된 추가 작업을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745238091331&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  func makeUIView(context: Context) -&amp;gt; MTKView {
    let mtkView = MTKView()
    mtkView.preferredFramesPerSecond = 30 // 초당 30번 MTKView를 그린다.
    mtkView.delegate = context.coordinator // Delegate는 Coordinator에서 구현한다.
    return mtkView
  }
  
  func updateUIView(_ uiView: MTKView, context: Context) {}
  
  func makeCoordinator() -&amp;gt; Coordinator {
    return Coordinator()
  }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745238120644&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // MARK: - Coordinator
  
  final class Coordinator: NSObject, MTKViewDelegate {
  
    func draw(in view: MTKView) {
      // 초당 30회 호출된다.
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초에 30번씩 draw 메서드를 타면서 MTKView에 무언가를 그릴 것이라는 것을 세팅했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 실제로 그리기 위한 준비를 해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745238799545&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  func makeUIView(context: Context) -&amp;gt; MTKView {
    ...	
    mtkView.device = MTLCreateSystemDefaultDevice() // Render에 사용할 GPU 객체 생성
    context.coordinator.setupMetal(view: mtkView) // 현재 MTKView로 Metal에 필요한 세팅
    return mtkView
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745238853133&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class Coordinator: NSObject, MTKViewDelegate {
  
  // MARK: - Properties
  
  private var device: MTLDevice!
  private var commandQueue: MTLCommandQueue!
  private var pipelineState: MTLRenderPipelineState!
  private var textureCache: CVMetalTextureCache!
  
  ...
}

extension Coordinator {
  func setupMetal(view: MTKView) {
    device = view.device
    commandQueue = device.makeCommandQueue()
    
    let library = device.makeDefaultLibrary()!
    let vertexFunc = library.makeFunction(name: &quot;vertexShader&quot;)
    let fragmentFunc = library.makeFunction(name: &quot;filterFragment&quot;)
    let pipelineDesc = MTLRenderPipelineDescriptor()
    
    pipelineDesc.vertexFunction = vertexFunc
    pipelineDesc.fragmentFunction = fragmentFunc
    pipelineDesc.colorAttachments[0].pixelFormat = view.colorPixelFormat
    pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDesc)
    CVMetalTextureCacheCreate(nil, nil, device, nil, &amp;amp;textureCache)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 Metal을 사용하기 위한 순서와 방법은 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GPU 생성 -&amp;gt; &lt;br /&gt;CommandQueue 생성 -&amp;gt;&lt;br /&gt;Shader(함수)를 정의 및 생성 -&amp;gt;&lt;br /&gt;어떤 파이프라인을 사용하고 어떤 포맷을 사용할 건지 생성 -&amp;gt; 여기까지가 setupMetal()&lt;br /&gt;CommandBuffer 생성 -&amp;gt;&lt;br /&gt;명령의 Encoder 생성 -&amp;gt;&lt;br /&gt;Encoder에 Shader 등록 및 파라미터 등록 -&amp;gt;&lt;br /&gt;어느 Drawable(View)에 그릴지 CommandBuffer에 지정 -&amp;gt;&lt;br /&gt;명령 commit -&amp;gt;&lt;br /&gt;GPU 연산 시작&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;간단한거 맞나?&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator의 setupMetal은 어떤 파이프라인을 사용할 것인지까지 미리 준비하는 메서드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 하고 실행해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2230&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPoiXB/btsNt66pJX3/YY2ASWDQngelpglyiBY6ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPoiXB/btsNt66pJX3/YY2ASWDQngelpglyiBY6ak/img.png&quot; data-alt=&quot;에러가 난다!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPoiXB/btsNt66pJX3/YY2ASWDQngelpglyiBY6ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPoiXB%2FbtsNt66pJX3%2FYY2ASWDQngelpglyiBY6ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2230&quot; height=&quot;186&quot; data-origin-width=&quot;2230&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에러가 난다!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러가 난다. (?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;let library = device.makeDefaultLibrary()!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 에러가 나는데, 위 코드는 프로젝트 내에 있는 Shader를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 아직 Shader를 추가하지 않았기 때문에 찾지 못해서 에러가 나는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shader를 작성하는 .metal 파일을 추가해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BWNQ3/btsNtOd3w68/OlvXkLwpKAGAco4f1ivIKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BWNQ3/btsNtOd3w68/OlvXkLwpKAGAco4f1ivIKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BWNQ3/btsNtOd3w68/OlvXkLwpKAGAco4f1ivIKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBWNQ3%2FbtsNtOd3w68%2FOlvXkLwpKAGAco4f1ivIKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1408&quot; height=&quot;384&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qvSIS/btsNttaaVDK/MbpcPzExG92vTGj7Im0PrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qvSIS/btsNttaaVDK/MbpcPzExG92vTGj7Im0PrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qvSIS/btsNttaaVDK/MbpcPzExG92vTGj7Im0PrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqvSIS%2FbtsNttaaVDK%2FMbpcPzExG92vTGj7Im0PrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;196&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 Metal 파일을 생성해주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745239881466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;metal_stdlib&amp;gt;
using namespace metal;

struct VertexOut {
  float4 position [[position]];
  float2 texCoord;
};

vertex VertexOut vertexShader(uint vid [[vertex_id]]) {
  float2 pos[4] = { {-1,-1}, {1,-1}, {-1,1}, {1,1} };
  float2 uv[4]  = { {0,1},    {1,1},    {0,0},    {1,0} };
  VertexOut out;
  out.position = float4(pos[vid], 0, 1);
  out.texCoord = uv[vid];
  return out;
}

fragment float4 filterFragment(
  VertexOut in [[stage_in]],
  texture2d&amp;lt;float&amp;gt; inTex [[texture(0)]]
  ) {
  constexpr sampler s(address::clamp_to_edge);
  float4 c = inTex.sample(s, in.texCoord);
  return float4(1.0 - c.rgb, c.a);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RenderPipeline이기 때문에 vertex shader와 fragment shader를 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vertex shader는 화면 전체를 덮는 사각형을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fragment shader는 사각형을 구성하는 모든 픽셀의 좌표 + 현재 촬영된 texture를 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;촬영된 texture를 픽셀의 좌표로 샘플링하고, 그 위치의 rgb에 반전을 연산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단하게 설명하면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vertex로 화면 전체를 덮는 사각형을 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fragment로 사각형의 각 픽셀에 현재 촬영된 이미지의 픽셀을 찾아서 해당 색을 반전해서 적용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 이렇게 이해하고 넘어가자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Texture를 촬영할 AVFoundation을 추가하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745241769254&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  func makeUIView(context: Context) -&amp;gt; MTKView {
    ...
    context.coordinator.setupCaptureSession(on: mtkView)
    return mtkView
  }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745241879902&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Coordinator

final class Coordinator: NSObject {
  
  // MARK: - Properties
  
  ...
  private var currentTexture: MTLTexture?
  private let captureSession = AVCaptureSession()
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745242013962&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Setup

extension Coordinator {
  func setupMetal(view: MTKView) {
    ...
  }
  
  func setupCaptureSession(on view: MTKView) {
    guard 
      let camera = AVCaptureDevice.default(
        .builtInWideAngleCamera, for: .video, position: .back
      ),
      let input = try? AVCaptureDeviceInput(device: camera) else {
      return 
    }
    captureSession.sessionPreset = .hd1280x720
    captureSession.addInput(input)
    
    let videoOutput = AVCaptureVideoDataOutput()
    videoOutput.videoSettings = [
      kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA
    ]
    videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: &quot;VideoQueue&quot;))
    captureSession.addOutput(videoOutput)
    
    // 카메라 방향 보정
    if let connection = videoOutput.connection(with: .video) {
      connection.videoOrientation = .portrait
      connection.isVideoMirrored = false
    }
    
    DispatchQueue.main.async { [weak self] in
      self?.captureSession.startRunning()
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1745242081862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate

extension Coordinator: AVCaptureVideoDataOutputSampleBufferDelegate {
  func captureOutput(
    _ output: AVCaptureOutput,
    didOutput sampleBuffer: CMSampleBuffer,
    from connection: AVCaptureConnection
    ) {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
    let width = CVPixelBufferGetWidth(pixelBuffer)
    let height = CVPixelBufferGetHeight(pixelBuffer)
    var cvTexture: CVMetalTexture?
    CVMetalTextureCacheCreateTextureFromImage(
      nil, 
      textureCache,
      pixelBuffer, 
      nil, 
      .bgra8Unorm,
      width, 
      height,
      0,
      &amp;amp;cvTexture
    )
    if let cvTex = cvTexture, let texture = CVMetalTextureGetTexture(cvTex) {
      currentTexture = texture
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라를 사용해서 촬영을 시작하고, 각 프레임별로 Delegate에 떨어질 때마다 currentTexture를 업데이트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;1369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmCQpc/btsNuvLktDY/P5GsanuwyPcsR3wvmoON9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmCQpc/btsNuvLktDY/P5GsanuwyPcsR3wvmoON9k/img.png&quot; data-alt=&quot;권한 요청을 잊지 말자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmCQpc/btsNuvLktDY/P5GsanuwyPcsR3wvmoON9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmCQpc%2FbtsNuvLktDY%2FP5GsanuwyPcsR3wvmoON9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;617&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;1369&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;권한 요청을 잊지 말자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lt;key&amp;gt;NSCameraUsageDescription&amp;lt;/key&amp;gt; &amp;lt;string&amp;gt;카메라 권한 요청 문구&amp;lt;/string&amp;gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 텍스쳐도 계속 업데이트 된다. 마지막으로 draw를 작성해주면 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1745242529200&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - MTKViewDelegate

extension Coordinator: MTKViewDelegate {
  func draw(in view: MTKView) {
    guard let drawable = view.currentDrawable,
          let texture = currentTexture,
          let desc = view.currentRenderPassDescriptor else { return }
    
    let commandBuffer = commandQueue.makeCommandBuffer()!
    let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: desc)!
    encoder.setRenderPipelineState(pipelineState)
    encoder.setFragmentTexture(texture, index: 0) // fragment shader로 텍스처 전달
    encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) // 4개의 점으로 전체 화면을 덮는 삼각형 2개 생성
    encoder.endEncoding() // 지금까지의 명령 인코딩
    commandBuffer.present(drawable) // 그려줄 drawable 등록 (MTKView)
    commandBuffer.commit() // 명령 커밋
  }
  
  func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정이 Metal(GPU)로 명령을 전달하고 실행하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 마치고 나면 완성된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;metal_1_result.gif&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ITC2r/btsNu0RAq2c/gseDJPeZuoIKk8or9Hc4nK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ITC2r/btsNu0RAq2c/gseDJPeZuoIKk8or9Hc4nK/img.gif&quot; data-alt=&quot;무서운 곰인형이 되었다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ITC2r/btsNu0RAq2c/gseDJPeZuoIKk8or9Hc4nK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ITC2r/btsNu0RAq2c/gseDJPeZuoIKk8or9Hc4nK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;584&quot; data-filename=&quot;metal_1_result.gif&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;623&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;무서운 곰인형이 되었다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 아주 간단한(?) Metal Sample 앱을 만들어 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 Metal에 관한 기초 내용이나 GPGPU를 활용한 앱을 게시해 봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드 (github)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/dudgkr2014/MetalSample_1_ColorInvert/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/dudgkr2014/MetalSample_1_ColorInvert/tree/main&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1745245114938&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dudgkr2014/MetalSample_1_ColorInvert: BLEV First Metal Sample App. Color Invert&quot; data-og-description=&quot;BLEV First Metal Sample App. Color Invert. Contribute to dudgkr2014/MetalSample_1_ColorInvert development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dudgkr2014/MetalSample_1_ColorInvert/tree/main&quot; data-og-url=&quot;https://github.com/dudgkr2014/MetalSample_1_ColorInvert&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kL0Es/hyYJwSGUey/LazCG2zVIeYqHrEkYGVov0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bAbAZn/hyYB80kSDr/mBu6IiCGKvP63FJ7qBWqRK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/dudgkr2014/MetalSample_1_ColorInvert/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dudgkr2014/MetalSample_1_ColorInvert/tree/main&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kL0Es/hyYJwSGUey/LazCG2zVIeYqHrEkYGVov0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bAbAZn/hyYB80kSDr/mBu6IiCGKvP63FJ7qBWqRK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dudgkr2014/MetalSample_1_ColorInvert: BLEV First Metal Sample App. Color Invert&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BLEV First Metal Sample App. Color Invert. Contribute to dudgkr2014/MetalSample_1_ColorInvert development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Metal</category>
      <category>c++</category>
      <category>github</category>
      <category>ios</category>
      <category>METAL</category>
      <category>sample</category>
      <category>Swift</category>
      <category>SwiftUI</category>
      <category>그래픽 api</category>
      <category>샘플</category>
      <category>예제</category>
      <author>BLEV</author>
      <guid isPermaLink="true">https://blev.tistory.com/1</guid>
      <comments>https://blev.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 21 Apr 2025 23:12:05 +0900</pubDate>
    </item>
  </channel>
</rss>