<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기록하는 블로그</title>
    <link>https://luckypaants.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 1 Jul 2026 01:16:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>운좋은바지</managingEditor>
    <item>
      <title>DTO, Domain, Local 모델링</title>
      <link>https://luckypaants.tistory.com/127</link>
      <description>&lt;h1&gt;TL;DR&lt;/h1&gt;
&lt;p&gt;RDB에서는 &lt;strong&gt;N쪽(Element)이 FK로 1쪽(Group)을 참조하는 구조가 기본&lt;/strong&gt;이다.&lt;br&gt;클라이언트나 ORM(CoreData, SwiftData 등)에서는 &lt;strong&gt;편의상 양방향 관계처럼 보일 수 있지만 실제 DB 구조는 동일하다.&lt;/strong&gt;  &lt;/p&gt;
&lt;hr&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Local --&amp;gt; Domain --&amp;gt; DTO --&amp;gt; RDB&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;DTO는 벡엔드와 프론트의 계약(Spec)&lt;/li&gt;
&lt;li&gt;DTO는 편의를 위해 join된 결과&lt;/li&gt;
&lt;li&gt;DTO는 정규화를 염두한 구조일 필요는 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;착각한 사실&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;RDB에서는 &lt;code&gt;Group&lt;/code&gt;이 &lt;code&gt;Element&lt;/code&gt; 목록을 직접 알고 있다고 생각했다.  &lt;/li&gt;
&lt;li&gt;또는 로컬 DB에서는 &lt;code&gt;Group → Element&lt;/code&gt; 구조가 기본이라고 생각했다.  &lt;/li&gt;
&lt;li&gt;DTO 구조가 nested라서 Domain 모델도 같은 구조여야 한다고 생각했다.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예시 (착각)  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Group  
 └ elements: [Element]  

Element  
 └ group을 모름  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;또는  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Group  
 └ element_ids  &lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h1&gt;정확한 정보&lt;/h1&gt;
&lt;h3&gt;RDB 기본 관계 (1:N)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Group  
- id (PK)  

Element  
- id (PK)  
- group_id (FK)  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;관계 방향  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Element → Group  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;즉  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Element&lt;/code&gt;는 &lt;code&gt;group_id&lt;/code&gt;를 가진다  &lt;/li&gt;
&lt;li&gt;&lt;code&gt;Group&lt;/code&gt;은 &lt;code&gt;Element&lt;/code&gt;를 직접 모르고  &lt;/li&gt;
&lt;li&gt;필요할 때 &lt;strong&gt;JOIN 또는 query로 가져온다&lt;/strong&gt;  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT *  
FROM element  
WHERE group_id = 1  &lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h1&gt;왜 이런 구조일까&lt;/h1&gt;
&lt;h3&gt;1️⃣ 정규화 (Normalization)&lt;/h3&gt;
&lt;p&gt;중복 데이터를 줄이기 위해  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Group  
- element_ids [1,2,3]  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;같은 구조를 사용하지 않는다.  &lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2️⃣ 데이터 변경 비용 최소화&lt;/h3&gt;
&lt;p&gt;만약 &lt;code&gt;Group&lt;/code&gt;이 element 목록을 가지고 있다면  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;element 추가  &lt;/li&gt;
&lt;li&gt;element 삭제  &lt;/li&gt;
&lt;li&gt;element 이동  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;할 때마다 &lt;strong&gt;Group row도 수정해야 한다.&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;하지만 FK 구조라면  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Element.group_id 변경  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;만 하면 된다.  &lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3️⃣ 확장성&lt;/h3&gt;
&lt;p&gt;이 구조는  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;indexing  &lt;/li&gt;
&lt;li&gt;join  &lt;/li&gt;
&lt;li&gt;sharding  &lt;/li&gt;
&lt;li&gt;pagination  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;모두에서 유리하다.  &lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;ORM  Local DB에서는 왜 반대로 보일까&lt;/h1&gt;
&lt;p&gt;예 (SwiftData / CoreData)  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GroupEntity  
- elements: [ElementEntity]  

ElementEntity  
- group: GroupEntity  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;코드에서는  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Group → elements  
Element → group  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;양방향 관계처럼 보인다.  &lt;/p&gt;
&lt;p&gt;하지만 실제 DB에서는  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Element.group_id  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;하나의 FK만 존재한다.  &lt;/p&gt;
&lt;p&gt;즉 &lt;strong&gt;ORM이 개발 편의를 위해 양방향 접근자를 제공하는 것뿐이다.&lt;/strong&gt;  &lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;활용 예시 (Client Architecture)&lt;/h1&gt;
&lt;p&gt;일반적인 앱 구조  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server  
 ├ DB Model  
 └ API DTO  

Client  
 ├ Domain Model  
 └ Local DB Model  &lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;1️⃣ DTO (API Response)&lt;/h2&gt;
&lt;p&gt;서버는 보통 nested 구조로 내려준다.  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{  
  &amp;quot;groups&amp;quot;: [  
    {  
      &amp;quot;id&amp;quot;: 1,  
      &amp;quot;elements&amp;quot;: [  
        { &amp;quot;id&amp;quot;: 1 },  
        { &amp;quot;id&amp;quot;: 2 }  
      ]  
    }  
  ]  
}  &lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2️⃣ Domain Model (Normalized)&lt;/h2&gt;
&lt;p&gt;클라이언트에서는 보통 flatten 한다.  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Group  
- id  

Element  
- id  
- groupId  &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;예시&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;elements.filter { $0.groupId == group.id }

Dictionary&amp;lt;groupId, [Element]&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3️⃣ Local DB&lt;/h2&gt;
&lt;p&gt;로컬 DB도 보통 동일한 구조를 사용한다.  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GroupEntity  
- id  

ElementEntity  
- id  
- groupId  &lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;4️⃣ 실제 사용 예&lt;/h2&gt;
&lt;p&gt;예: 특정 그룹의 element 가져오기  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;elements.filter { $0.groupId == group.id }  &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는  &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Dictionary&amp;lt;GroupID, [Element]&amp;gt;  &lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h1&gt;최종 정리&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;레이어&lt;/th&gt;
&lt;th&gt;구조&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;RDB&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Element.group_id → Group.id&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DTO&lt;/td&gt;
&lt;td&gt;nested 구조 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain&lt;/td&gt;
&lt;td&gt;normalized (&lt;code&gt;groupId&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local DB&lt;/td&gt;
&lt;td&gt;RDB와 유사&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;핵심  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RDB는 항상 N쪽이 FK로 1쪽을 참조한다.&lt;/strong&gt;&lt;/p&gt;</description>
      <category>BE</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/127</guid>
      <comments>https://luckypaants.tistory.com/127#entry127comment</comments>
      <pubDate>Mon, 16 Mar 2026 19:59:25 +0900</pubDate>
    </item>
    <item>
      <title>Xcode 26.2 upgrade 후, build error @_lifetime experimental feature</title>
      <link>https://luckypaants.tistory.com/126</link>
      <description>&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;TCA와 tuist가 적용된 프로젝트&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;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OS 26.2로 업데이트 후, 빌드 되던 프로젝트가 빌드 에러.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCA에서 사용하는 apple 공식 라이브러리인 swift-collections에서 @_lifetime attribute에서 빌드 에러 발생&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;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;swift 6.2 이후 compiler flag 조건으로 활성화된 -lifetime attribute (experimental feature)가 활성화되지 않음&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;b&gt;해결 방법&lt;/b&gt;:&amp;nbsp;&lt;br /&gt;TL; DR: 디팬던시 지우고 재설치&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9GO5/dJMcahptCG6/XARHDwkESPpEkUvvKIzY9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9GO5/dJMcahptCG6/XARHDwkESPpEkUvvKIzY9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9GO5/dJMcahptCG6/XARHDwkESPpEkUvvKIzY9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9GO5%2FdJMcahptCG6%2FXARHDwkESPpEkUvvKIzY9k%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;1996&quot; height=&quot;1304&quot; data-origin-width=&quot;1996&quot; data-origin-height=&quot;1304&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;하지만 라이브러리 수정을 위해 unlock해야하고, tuist에서 tuist generate 시에 매번 적용 및 해야함 CI시에도 불가능함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lifetimeoveride.swift 파일의 주석에서 autogenerate된다고 해서 지워보니 해당 attribute가 swift compiler flag에서 적용된채로 프레임워크가 로드됨&lt;/p&gt;</description>
      <category>iOS/이슈</category>
      <category>experimental feature</category>
      <category>Lifetimes</category>
      <category>swift collection</category>
      <category>tca</category>
      <category>Tuist</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/126</guid>
      <comments>https://luckypaants.tistory.com/126#entry126comment</comments>
      <pubDate>Sun, 21 Dec 2025 20:21:31 +0900</pubDate>
    </item>
    <item>
      <title>iOS 개발자의 React 공부하며 느낀 점</title>
      <link>https://luckypaants.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;iOS를 하다 React 개발을 하게 되어 공부를 하고있는데 다른 것도 많고 비슷한 것도 많았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공통점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI에서 State/Observable 등을 통한 State 동기화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flux 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Environment와 Context를 통해 dependency 전달할 때 편리하게 관리&lt;/li&gt;
&lt;li&gt;Stateful / Stateless component&lt;/li&gt;
&lt;li&gt;가상 Dom을 활용한 부분 렌더링 교체 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwiftUI는 가상돔은 아니지만 tree의 diff 연산을 통해 부분 교체하는 점이 닮은 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;tuist/pnpm(workspace) 등의 모노레포 트렌드&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;차이점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트렌드의 빠른 변화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSR이었다가 CSR이었다가 이제는 또 다시 SSR로 변경되었다. SSR로 다시 돌아왔다. 물론 SSR이 필수는 아니다.&lt;/li&gt;
&lt;li&gt;이 때문에 풀스택 개발자가 되어버렸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lint, Format(Prettier), husky(git hook) 등의 툴이 발달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 생태계도 SwiftFormat, SwiftLint 등이 있지만 웹프론트 생태계가 적극적으로 프로젝트에 비해 아직 선택의 영역인 것 같다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;typescript를 활용하여 type check가 이뤄지긴 하지만 swift에 비해 type safe하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;type 때문에 발생하는 런타임 에러가 많음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;React가 너무 프레임워크 의존적임
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음 Learning React를 보며 학습했는데 &lt;b&gt;Create React App&lt;/b&gt;이 deprecated되고, framework 의존적되게 되었다. 예를 들어, Next.js 또는 React-Router 빌드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;너무 많은 프레임워크/라이브러리&lt;br /&gt;너무 많아서 뭘로 시작해야할지 머리가 아팠다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드: Webpack, Vite&lt;/li&gt;
&lt;li&gt;상태 관리: Recoil, Zustand, TanStack Query ..&lt;/li&gt;
&lt;li&gt;React Router, Next.js's AppRouter, TanStack Router&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀점&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다른 언어를 배운다는 것&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 Swift만 하다가 다른 언어를 배운다는 게 겁이 났다. 새로운 문법, 새로운 프레임워크 등... 아직은 익히는 중이지만, 한 언어를 깊게 공부하면 다른 언어를 배우는 것의 난이도가 내려간다는 것을 느끼는 중이다. syntax가 다르지만, 코드를 통해 구현하려는 것은 같다는 것을 느꼈다. 또한, 메모리 관리, 동기화, OOP, 의존성 관리 등 프로그래밍 언어 그 자체가 가지는 개념은 비슷한 것 같다. &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;다른 언어를 배우면서 오히려 swift의 특장점도 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;swif를 배울 때는 왜 당연한 걸 특징이라고 하면서 강조하지 했는데 다른 언어는 그렇지 않다는 것도 배우게 됐다. 특히 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;swift가 모던 프로그래밍 언어라서 제공하는 점이 많다는 것도 깨달았다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;오픈소스 생태계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹개발자의 수가 앱개발자에 비해 많다보니 오픈소스 생태계가 매우 활발하다를 넘어서 주도하는 것 같다. iOS가 오픈소스가 비교적 제한되어있는데 반해 오픈소스 생태계가 발달한 만큼 &lt;b&gt;왜 이 기술스택을 선택했는지&lt;/b&gt;가 중요한 것 같다.&amp;nbsp; &lt;br /&gt;러닝 커브, 팀 역량, 프로젝트의 복잡도, 앞으로의 전망 등 선택의 이유가 명확해야 한다. 해당 라이브러리/프레임워크의 철학이나 작동 방식, 추가 지원 기능 등 상당히 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 iOS에서는 특정 오픈소스가 독점하고 있거나, 오픈소스의 유지보수를 신뢰하지 못하는 이유로 네이티브를 선호해서 사내에서 따로 개발해서 사용한다. 그래서 선택의 이유가 물론 있지만 웹프론트만큼 중요하진 않은 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;배포 및 폐쇄성&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS는 애플 스토어를 통해 배포하기 때문에 심사를 거쳐야한다. 때문에 빠른 배포의 불편, 저작권 심사 규정이라던가, 빠른 아이디어 실현 등이 웹에 비해서 제한적이다. 대신에 앱에서 동작하기 때문에 호스팅을 걱정할 필요도 없고, 앱 스토어 생태계가 잘 구축되어 있고 수익화도 가능하다. 앱 클립을 통한&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;웹앱&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모바일 웹앱은 기능이 제한적이거나 매우 느리다고 생각해왔는데 이번에 웹앱 서비스들을 많이 사용해보며 기존의 생각이 틀렸다는 걸 깨달았다. &lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹앱은 설치가 필요없이 링크를 통해 바로 앱을 사용할 수 있고, 심사를 거치지 않기 때문에 배포하면 바로 반영된다. 저작권 등 콘텐츠 제한적인 부분에서 매우 자유롭다. 웹앱 몇개를 사용해봤는데 &lt;u&gt;SNS 로그인, 콘텐츠 편집, 미디어 재생 등 앱&lt;/u&gt;과 비슷한 경험을 제공하여 놀랐다. 또한, 속도 또한 매우 빨랐는데 iOS처럼 자연스러운 애니메이션까지는 아니었지만 과거 웹뷰를 생각했을 때와는 전혀 다른 경험이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 사실상 &lt;u&gt;대부분의 서비스&lt;/u&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;반면 단점도 컷는데 오픈 소스 생태계가 편해진 만큼 의존성이 너무 커져서, 단순한 웹앱도 호스팅이 필요하고 구독이 필요하게 된 거 같다. 레딧을 글을 봤는데 어떻게 하면 vercel에서 coolify, render같은 플랫폼으로 마이그레이션하는지 고민하는 글에 댓글이 107개나 달렸다.&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;u&gt;유저 경험을 극대화한&lt;/u&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 / 위젯 / 액티비티 킷 통한 앱 유저 경험 극대화&lt;/li&gt;
&lt;li&gt;알림(PWA는 가능하다고 함)&lt;/li&gt;
&lt;li&gt;카메라 및 위치 모션 헬스 센서&lt;/li&gt;
&lt;li&gt;백그라운드 잡 / Persistant Storage 등을 offline-first 앱&lt;/li&gt;
&lt;li&gt;AR / 고성능 렌더링(Rust 통해 웹도 가능) / 애니메이션 등 디바이스 자원 활용&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>하루</category>
      <category>frontend</category>
      <category>ios</category>
      <category>REACT</category>
      <category>Swift</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/125</guid>
      <comments>https://luckypaants.tistory.com/125#entry125comment</comments>
      <pubDate>Sat, 29 Nov 2025 20:48:38 +0900</pubDate>
    </item>
    <item>
      <title>[HIG] UX Writing - 인터페이스를 위한 글쓰기 WWDC 2022</title>
      <link>https://luckypaants.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Developer Challenge의 다른 추천 영상을 보고 감상 및 정리! WWDC 2022&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;룰 - 프레임워크&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;P&lt;/b&gt;urpose&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A&lt;/b&gt;nticipation&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C&lt;/b&gt;ontext&lt;/li&gt;
&lt;li&gt;&lt;b&gt;E&lt;/b&gt;mpathy&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Purpose&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Consider information hierarchy&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTDkTz/dJMcagYbNTn/O3AU7Av5a2une9rZSTJ6jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTDkTz/dJMcagYbNTn/O3AU7Av5a2une9rZSTJ6jK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTDkTz/dJMcagYbNTn/O3AU7Av5a2une9rZSTJ6jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTDkTz%2FdJMcagYbNTn%2FO3AU7Av5a2une9rZSTJ6jK%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;1376&quot; height=&quot;746&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저의 시선을 고려해서 타이틀 / 설명 / 버튼을 배치해라&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Know What you leave out&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/0mt4Z/dJMcacIfVbP/OblkAnhKwEVeDMbflmzxg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0mt4Z/dJMcacIfVbP/OblkAnhKwEVeDMbflmzxg1/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0mt4Z/dJMcacIfVbP/OblkAnhKwEVeDMbflmzxg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0mt4Z%2FdJMcacIfVbP%2FOblkAnhKwEVeDMbflmzxg1%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blKPvH/dJMb99Sivgj/yO0UHJRod8prT9Hnv3CfQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blKPvH/dJMb99Sivgj/yO0UHJRod8prT9Hnv3CfQK/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blKPvH/dJMb99Sivgj/yO0UHJRod8prT9Hnv3CfQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblKPvH%2FdJMb99Sivgj%2FyO0UHJRod8prT9Hnv3CfQK%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/blockquote&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;h3 data-ke-size=&quot;size23&quot;&gt;새로운 기능을 소개할 때!&lt;/h3&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-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJzCM6/dJMcah3RIaw/yN4zsTFkrqEChd70IpkOI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJzCM6/dJMcah3RIaw/yN4zsTFkrqEChd70IpkOI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJzCM6/dJMcah3RIaw/yN4zsTFkrqEChd70IpkOI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJzCM6%2FdJMcah3RIaw%2FyN4zsTFkrqEChd70IpkOI0%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;1376&quot; height=&quot;746&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;Have a purpose for every screen&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PqUxH/dJMb99Y31YV/WkKKNdrAkJuhzxfvlHrCX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PqUxH/dJMb99Y31YV/WkKKNdrAkJuhzxfvlHrCX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PqUxH/dJMb99Y31YV/WkKKNdrAkJuhzxfvlHrCX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPqUxH%2FdJMb99Y31YV%2FWkKKNdrAkJuhzxfvlHrCX0%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;1376&quot; height=&quot;746&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플 페이의 마지막 스텝이다. 실제 등록 처리에 시간이 걸리고 완료되면 알림을 준다는 화면이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Anticipation&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Think of a conversation&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;/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/baArJX/dJMcaf528OG/Fuiw7QW9jXiagwmRCZ9100/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baArJX/dJMcaf528OG/Fuiw7QW9jXiagwmRCZ9100/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baArJX/dJMcaf528OG/Fuiw7QW9jXiagwmRCZ9100/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaArJX%2FdJMcaf528OG%2FFuiw7QW9jXiagwmRCZ9100%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhSjW/dJMcagcOIlC/U5KjamiJDoXBS8jSyNDrGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhSjW/dJMcagcOIlC/U5KjamiJDoXBS8jSyNDrGK/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhSjW/dJMcagcOIlC/U5KjamiJDoXBS8jSyNDrGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnhSjW%2FdJMcagcOIlC%2FU5KjamiJDoXBS8jSyNDrGK%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/div&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;다음은 락스크린 화면이다. 잘 자라는 말 / 슬립 모드 작동 중 / 다음 알람이 표시된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHZJmR/dJMcab3Edkx/ctfsS6qdOkHYUXU61h9Gr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHZJmR/dJMcab3Edkx/ctfsS6qdOkHYUXU61h9Gr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHZJmR/dJMcab3Edkx/ctfsS6qdOkHYUXU61h9Gr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHZJmR%2FdJMcab3Edkx%2FctfsS6qdOkHYUXU61h9Gr1%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;1376&quot; height=&quot;746&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;알람 시작 전 폰을 사용하고 있으면 다음과 같은 알림이 뜬다. 알람을 끌 것인지 그대로 둘 것인지 이러한 UX를 통해 유저와 대화하고 더 좋은 경험을 만들어낼 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Develop your app's voice first! and vary your tone&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로에게 질문하는 걸로 시작해보자. 무엇을 말해야하고 말하지 말하면 안될까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임앱이라면 재미와 흥미가 중요하겠지?&lt;/li&gt;
&lt;li&gt;뱅킹앱이라면 신뢰성과 안정성이 필요하겠지?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;당신이 누구와 대화를 하고 있는 건지 생각해보는 과정을 톹해 우리가 사용해야하는 단어를 찾아보자!&lt;/li&gt;
&lt;li&gt;공통으로 사용할 단어 목록을 만들자&lt;/li&gt;
&lt;/ol&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/IA759/dJMb995PGFj/GUDca6oAa8ImbMdEzbSR21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IA759/dJMb995PGFj/GUDca6oAa8ImbMdEzbSR21/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IA759/dJMb995PGFj/GUDca6oAa8ImbMdEzbSR21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIA759%2FdJMb995PGFj%2FGUDca6oAa8ImbMdEzbSR21%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPYPwq/dJMcabbvxuF/3j7O2w4FbkQcjhlXKw5XtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPYPwq/dJMcabbvxuF/3j7O2w4FbkQcjhlXKw5XtK/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPYPwq/dJMcabbvxuF/3j7O2w4FbkQcjhlXKw5XtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPYPwq%2FdJMcabbvxuF%2F3j7O2w4FbkQcjhlXKw5XtK%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/div&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;유저의 놀라운 성과를 칭찬하기도 하고, 위험을 경고하기도 한다. 동시에 너무 남용하면 효과가 떨어진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Know what comes next&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UX Writing할 때 반드시 '다음에는 뭐가 오지'하고 고민해야한다&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/SqYFx/dJMcaajmXMy/goBPL6SwTJgckrgKOpxlB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SqYFx/dJMcaajmXMy/goBPL6SwTJgckrgKOpxlB0/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&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/SqYFx/dJMcaajmXMy/goBPL6SwTJgckrgKOpxlB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSqYFx%2FdJMcaajmXMy%2FgoBPL6SwTJgckrgKOpxlB0%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckSkqw/dJMcagYbOy2/ViEkK6rIk2rwinIteaKjAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckSkqw/dJMcagYbOy2/ViEkK6rIk2rwinIteaKjAk/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&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/ckSkqw/dJMcagYbOy2/ViEkK6rIk2rwinIteaKjAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckSkqw%2FdJMcagYbOy2%2FViEkK6rIk2rwinIteaKjAk%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6SZp3/dJMcafkF0gX/8GE264GJOXVWzeGEM9I6o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6SZp3/dJMcafkF0gX/8GE264GJOXVWzeGEM9I6o1/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6SZp3/dJMcafkF0gX/8GE264GJOXVWzeGEM9I6o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6SZp3%2FdJMcafkF0gX%2F8GE264GJOXVWzeGEM9I6o1%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지도앱의 경우, 다음 경로를 예상해서 알려준다.&lt;/li&gt;
&lt;li&gt;명상앱의 경우, 인터렉티브한 이미지를 통해 무엇을 해야할지 직관적으로 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Think outside the app&lt;/b&gt;&lt;/blockquote&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/uGDUY/dJMb99LwTdV/86A80OS37O5wtgJiZnAxOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uGDUY/dJMb99LwTdV/86A80OS37O5wtgJiZnAxOk/img.png&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&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/uGDUY/dJMb99LwTdV/86A80OS37O5wtgJiZnAxOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuGDUY%2FdJMb99LwTdV%2F86A80OS37O5wtgJiZnAxOk%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwUmX6/dJMb99LwTdM/tGErSlzfUry61udSwoNS51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwUmX6/dJMb99LwTdM/tGErSlzfUry61udSwoNS51/img.png&quot; style=&quot;width: 32.5581%; margin-right: 10px;&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;33.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwUmX6/dJMb99LwTdM/tGErSlzfUry61udSwoNS51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwUmX6%2FdJMb99LwTdM%2FtGErSlzfUry61udSwoNS51%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDUGve/dJMcacO1hWk/AfYbvYbtXYGKlsbAFwrhC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDUGve/dJMcacO1hWk/AfYbvYbtXYGKlsbAFwrhC1/img.png&quot; style=&quot;width: 32.5581%;&quot; data-origin-width=&quot;1376&quot; data-origin-height=&quot;746&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDUGve/dJMcacO1hWk/AfYbvYbtXYGKlsbAFwrhC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDUGve%2FdJMcacO1hWk%2FAfYbvYbtXYGKlsbAFwrhC1%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;1376&quot; height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 아니라 현재 상황을 보고 Context를 조절해야한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파노라마앱에서는 파노라마 화살표 아래에 설명을 달아두었다. 유저가 그 화살표(정중앙)에 시선이 가기 때문&lt;/li&gt;
&lt;li&gt;운동앱의 경우 운동전에는 화면의 1/3을 차지하는 버튼이 노출되지만, 운동이 종료되고 나서는 다양한 정보를 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Write helpful alerts&lt;/b&gt;&lt;/blockquote&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/lAr0h/dJMcacIfYfD/Gblsd458BFwbfg8rXHHtV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAr0h/dJMcacIfYfD/Gblsd458BFwbfg8rXHHtV0/img.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;804&quot; data-is-animation=&quot;false&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/lAr0h/dJMcacIfYfD/Gblsd458BFwbfg8rXHHtV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAr0h%2FdJMcacIfYfD%2FGblsd458BFwbfg8rXHHtV0%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;520&quot; height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qKtKz/dJMcabo2HH3/lkQTY9XvhwneDdEXF3vDTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qKtKz/dJMcabo2HH3/lkQTY9XvhwneDdEXF3vDTK/img.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;804&quot; data-is-animation=&quot;false&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/qKtKz/dJMcabo2HH3/lkQTY9XvhwneDdEXF3vDTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqKtKz%2FdJMcabo2HH3%2FlkQTY9XvhwneDdEXF3vDTK%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;520&quot; height=&quot;804&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mz9i6/dJMcafZhAgt/wsjK5c8MxNPmgM6ckwDnt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mz9i6/dJMcafZhAgt/wsjK5c8MxNPmgM6ckwDnt0/img.png&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;804&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.5581%;&quot; data-widthpercent=&quot;33.34&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mz9i6/dJMcafZhAgt/wsjK5c8MxNPmgM6ckwDnt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmz9i6%2FdJMcafZhAgt%2FwsjK5c8MxNPmgM6ckwDnt0%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;520&quot; height=&quot;804&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;얼럿은 유저에게 도움이 되어야하며, 방해가 되면 안된다.&lt;/li&gt;
&lt;li&gt;의도가 명확해야하며, 혼란을 주면 안된다.&lt;/li&gt;
&lt;li&gt;유저에게 도움이 되는 정보를 제공해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Create useful empty states&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&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/CoPxs/dJMcaa4JYau/WFhqEBgmMDrqncCSKDMfxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CoPxs/dJMcaa4JYau/WFhqEBgmMDrqncCSKDMfxk/img.png&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;173&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;48.02&quot; data-filename=&quot;blob&quot; style=&quot;width: 47.4629%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CoPxs/dJMcaa4JYau/WFhqEBgmMDrqncCSKDMfxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCoPxs%2FdJMcaa4JYau%2FWFhqEBgmMDrqncCSKDMfxk%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;252&quot; height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clYUvR/dJMcahJy2Cz/LcwNhgJRJMWlzQJfwDNqS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clYUvR/dJMcahJy2Cz/LcwNhgJRJMWlzQJfwDNqS1/img.png&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;163&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.98&quot; data-filename=&quot;blob&quot; style=&quot;width: 51.3743%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clYUvR/dJMcahJy2Cz/LcwNhgJRJMWlzQJfwDNqS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclYUvR%2FdJMcahJy2Cz%2FLcwNhgJRJMWlzQJfwDNqS1%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;257&quot; height=&quot;163&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관용어 대신 번역이 쉬운 단어 선택&lt;/li&gt;
&lt;li&gt;실제로 도움이 되는 설명 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Empathy&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;Write for everyone&lt;br /&gt;Be responsive localization&lt;br /&gt;Design for accessibility&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유머대신 간단하고 명백한 단어 사용&lt;/li&gt;
&lt;li&gt;언어 별로 뷰 크기를 다르게 반응하게 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tXGzY/dJMcaap8p3j/fKKBMKUth1P1kt05KahsyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tXGzY/dJMcaap8p3j/fKKBMKUth1P1kt05KahsyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tXGzY/dJMcaap8p3j/fKKBMKUth1P1kt05KahsyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtXGzY%2FdJMcaap8p3j%2FfKKBMKUth1P1kt05KahsyK%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;856&quot; height=&quot;566&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;566&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;소외감이 들지 않게 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Read your writing out loud  &lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람에게 공유함으로 써 불필요한 또는 반복되는 문장, 오타 등의 디테일 문장등을 수정할 수 있다!&lt;/p&gt;</description>
      <category>iOS/WWDC 파보기</category>
      <category>UX Writing</category>
      <category>WWDC 2022</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/124</guid>
      <comments>https://luckypaants.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 17 Nov 2025 16:34:29 +0900</pubDate>
    </item>
    <item>
      <title>[HIG] UX Writing - 작은 텍스트 변화로 UI에 큰 임팩트 만들기</title>
      <link>https://luckypaants.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;애플 디벨로퍼 챌린지 중에 글쓰기에 관한 WWDC를 추천받아 감상을 남겨본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;룰&lt;/h2&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;- 단어 리스트 만들기&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수식어 제거 | Remove filler&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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/bGZKa9/dJMcabbtZ6P/VXhkFAw7ZDM4bKg3MJKWD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGZKa9/dJMcabbtZ6P/VXhkFAw7ZDM4bKg3MJKWD1/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGZKa9/dJMcabbtZ6P/VXhkFAw7ZDM4bKg3MJKWD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGZKa9%2FdJMcabbtZ6P%2FVXhkFAw7ZDM4bKg3MJKWD1%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKjGWR/dJMcabWRepb/KVaxFBBB44tNU7KxjEQi3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKjGWR/dJMcabWRepb/KVaxFBBB44tNU7KxjEQi3K/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKjGWR/dJMcabWRepb/KVaxFBBB44tNU7KxjEQi3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKjGWR%2FdJMcabWRepb%2FKVaxFBBB44tNU7KxjEQi3K%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수식어를 사용해야하는 예&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 기능인 경우&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/cFDaE5/dJMcahJxtqL/lnSdAy7QNbUHAq3BwdnMJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFDaE5/dJMcahJxtqL/lnSdAy7QNbUHAq3BwdnMJ1/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFDaE5/dJMcahJxtqL/lnSdAy7QNbUHAq3BwdnMJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFDaE5%2FdJMcahJxtqL%2FlnSdAy7QNbUHAq3BwdnMJ1%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn62Ao/dJMcain8XMj/KMQZKuXoemhdDbONq7qwxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn62Ao/dJMcain8XMj/KMQZKuXoemhdDbONq7qwxK/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn62Ao/dJMcain8XMj/KMQZKuXoemhdDbONq7qwxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn62Ao%2FdJMcain8XMj%2FKMQZKuXoemhdDbONq7qwxK%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;444&quot; height=&quot;317&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;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 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/dohCyi/dJMcagw5WoO/kS7LfJDGUokmzxBFHM72y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dohCyi/dJMcagw5WoO/kS7LfJDGUokmzxBFHM72y0/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dohCyi/dJMcagw5WoO/kS7LfJDGUokmzxBFHM72y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdohCyi%2FdJMcagw5WoO%2FkS7LfJDGUokmzxBFHM72y0%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HxN8v/dJMcacVLtwg/ctakJWCQMKIWuGZYVzuuF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HxN8v/dJMcacVLtwg/ctakJWCQMKIWuGZYVzuuF0/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HxN8v/dJMcacVLtwg/ctakJWCQMKIWuGZYVzuuF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHxN8v%2FdJMcacVLtwg%2FctakJWCQMKIWuGZYVzuuF0%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;444&quot; height=&quot;317&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;감탄사, 감사/사과 문구, 느낌표 등은 사용할 경우, 그 의미가 가벼워보이지 않는지, 주요 내용에 방해가 되는지 고민해봐야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;반복 피하기 | Avoid repetition&lt;/h2&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/cwVjlO/dJMcagw5WtP/8sMdPtaf8lVO42grKolYjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwVjlO/dJMcagw5WtP/8sMdPtaf8lVO42grKolYjK/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwVjlO/dJMcagw5WtP/8sMdPtaf8lVO42grKolYjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwVjlO%2FdJMcagw5WtP%2F8sMdPtaf8lVO42grKolYjK%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cACk9O/dJMcabJj3iV/nj7uKcRGBjiPSiBwUwuqgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cACk9O/dJMcabJj3iV/nj7uKcRGBjiPSiBwUwuqgk/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cACk9O/dJMcabJj3iV/nj7uKcRGBjiPSiBwUwuqgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcACk9O%2FdJMcabJj3iV%2Fnj7uKcRGBjiPSiBwUwuqgk%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;444&quot; height=&quot;317&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;blockquote data-ke-style=&quot;style1&quot;&gt;UX Writing은 언어의 경제학(덜음의 미학?)이다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목적을 언급하며 시작하기 | Lead with the why&lt;/h2&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-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1Jlzm/dJMcain8XT1/JFC9i43y7WlYfoUHuS0Vv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1Jlzm/dJMcain8XT1/JFC9i43y7WlYfoUHuS0Vv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1Jlzm/dJMcain8XT1/JFC9i43y7WlYfoUHuS0Vv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1Jlzm%2FdJMcain8XT1%2FJFC9i43y7WlYfoUHuS0Vv1%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;444&quot; height=&quot;317&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;To do or get one thing, &lt;br /&gt;first do another thing&lt;/span&gt;&lt;/blockquote&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/n5CSz/dJMcafZf1oB/RxV3p19zmcN4lLpRxkCOxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n5CSz/dJMcafZf1oB/RxV3p19zmcN4lLpRxkCOxk/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n5CSz/dJMcafZf1oB/RxV3p19zmcN4lLpRxkCOxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn5CSz%2FdJMcafZf1oB%2FRxV3p19zmcN4lLpRxkCOxk%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpiBwH/dJMcabJj3pU/5dkWgf1wLdxwYB3j54fbD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpiBwH/dJMcabJj3pU/5dkWgf1wLdxwYB3j54fbD1/img.png&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpiBwH/dJMcabJj3pU/5dkWgf1wLdxwYB3j54fbD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpiBwH%2FdJMcabJj3pU%2F5dkWgf1wLdxwYB3j54fbD1%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;444&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단어 목록 만들기 | Make a word list&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 주제를 여러가지 예시와 다른 말들로 써내려가는 에세이와 다르게, 앱 UX에서는 동의어를 피하고 한 가지 단어를 사용하는 것이 사영성과 명확성을 강화할 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4spY7/dJMcaihnprh/ppy7vqZbUTQ7SFfRvUEU80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4spY7/dJMcaihnprh/ppy7vqZbUTQ7SFfRvUEU80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4spY7/dJMcaihnprh/ppy7vqZbUTQ7SFfRvUEU80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4spY7%2FdJMcaihnprh%2Fppy7vqZbUTQ7SFfRvUEU80%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;838&quot; height=&quot;333&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;333&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;838&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgkCeS/dJMcafrpOH6/pEI0egTJjm6ecYS4Xnl5B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgkCeS/dJMcafrpOH6/pEI0egTJjm6ecYS4Xnl5B1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgkCeS/dJMcafrpOH6/pEI0egTJjm6ecYS4Xnl5B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgkCeS%2FdJMcafrpOH6%2FpEI0egTJjm6ecYS4Xnl5B1%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;838&quot; height=&quot;333&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Username, handle 대신 Alias라는 단어를 사용하였다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단어 선정에도 많은 고민이 필요하다는 걸 알게 되었다. 언제나 그렇지만 더하기보다는 빼기를 잘하는 것이 중요하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A/B 테스트를 통해 다른 문구를 사용한다고 하는데, 다 이런 고민들과 마케팅 기법들이 들어간 거 같다.&lt;/p&gt;</description>
      <category>iOS/WWDC 파보기</category>
      <category>HIG</category>
      <category>ux</category>
      <category>WWDC</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/123</guid>
      <comments>https://luckypaants.tistory.com/123#entry123comment</comments>
      <pubDate>Fri, 14 Nov 2025 11:34:15 +0900</pubDate>
    </item>
    <item>
      <title>[코딩테스트]   컨베이어 벨트 위 로봇 - 백준 20055</title>
      <link>https://luckypaants.tistory.com/121</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/20055&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제링크&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;난이도: 골드 5&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;유형: 구현, 시뮬레이션&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;소요시간: 1시간 + GPT&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;며칠 안 풀었다고 머리가 굳은 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let NK = readLine()!.split(separator: &quot; &quot;).map { Int($0)! }
let (N, K) = (NK[0], NK[1])
var A = readLine()!.split(separator: &quot; &quot;).map { Int($0)! }

var robot = [Bool](repeating: false, count: N)

var start = 0

let size = 2 * N
var step = 0

func beltIndex(_ start: Int, _ posOnTop: Int, _ size: Int) -&amp;gt; Int {
    (start + posOnTop) % size
}

while true {
    step += 1

    // 회전
    start = (start - 1 + size) % size

    // 로봇 회전
    for i in stride(from: N - 1, through: 1, by: -1) {
        robot[i] = robot[i - 1]
    }

    robot[0] = false
    robot[N - 1] = false

    // 로봇 이동
    for i in stride(from: N - 2, through: 0, by: -1) {
        if robot[i] &amp;amp;&amp;amp; !robot[i + 1] {
            let nextIndex = beltIndex(start, i + 1, size)
            if A[nextIndex] &amp;gt; 0 {
                robot[i] = false
                robot[i + 1] = true
                A[nextIndex] -= 1
            }
        }
    }

    robot[N - 1] = false

    let upIndex = beltIndex(start, 0, size)

    if !robot[0] &amp;amp;&amp;amp; A[upIndex] &amp;gt; 0 {
        robot[0] = true
        A[upIndex] -= 1
    }

    if A.filter({ $0 == 0 }).count &amp;gt;= K {
        print(step)
        break
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>구현</category>
      <category>시뮬레이션</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/121</guid>
      <comments>https://luckypaants.tistory.com/121#entry121comment</comments>
      <pubDate>Mon, 10 Nov 2025 11:55:48 +0900</pubDate>
    </item>
    <item>
      <title>[코딩테스트] 신고결과받기 - Kakao 2022 BLIND</title>
      <link>https://luckypaants.tistory.com/120</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/92334?language=swift&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제링크&lt;/a&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;난이도&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;유형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소요시간&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20분&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오랜만에 푸는 문제여서 레벨 1로 워밍업했다. 중간에 런타임 에러 나는 케이스가 있어서&amp;nbsp;&lt;br /&gt;index 접근 런타임 에러 방지를 위해 길이가 0인 경우 탈출문을 추가했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import Foundation

func solution(_ id_list:[String], _ report:[String], _ k:Int) -&amp;gt; [Int] {

    // 신고 여부 테이블
    var idTable: [String:Set&amp;lt;String&amp;gt;] = [:]

    // 차단 테이블
    var reportTable: [String: Int] = [:]

    report
        .map { $0.split(separator: &quot; &quot;) }
        .forEach { value in
            if value.isEmpty { return }
            let (id, target) = (String(value[0]), String(value[1]))

            if !idTable[id, default: []].contains(target) {
                reportTable[target, default: 0] += 1
                idTable[id, default: []].insert(target)
            }
        }

    // id: set id에서 차단 갯수 reduce

    return id_list
        .map { id in
            let reports = idTable[id, default: []]
            return reports.reduce(into: 0) { acc, next in
                acc += reportTable[next, default: 0] &amp;gt;= k ? 1 : 0
            }
        }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>kakao</category>
      <category>알고리즘</category>
      <category>카카오</category>
      <category>코딩테스트</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/120</guid>
      <comments>https://luckypaants.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 5 Nov 2025 23:58:56 +0900</pubDate>
    </item>
    <item>
      <title>[코딩테스트]   아기 상어 - BJ16236</title>
      <link>https://luckypaants.tistory.com/119</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/16236&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제링크&lt;/a&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;난이도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;골드 3&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현, BFS&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소요시간&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1시간 + GPT&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘은 BFS인데, 상태관리와 자잘한 조건 처리가 까다로웠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;
let N = Int(readLine()!)!
var board = [[Int]]()
var sharkR = 0, sharkC = 0

for i in 0..&amp;lt;N {
    let row = readLine()!.split(separator: &quot; &quot;).map { Int($0)! }
    board.append(row)
    if let j = row.firstIndex(of: 9) {
        sharkR = i; sharkC = j
        board[i][j] = 0
    }
}

let dr = [-1, 0, 0, 1]
let dc = [0, -1, 1, 0]

func bfs(_ sr: Int, _ sc: Int, _ size: Int) -&amp;gt; (Int, Int, Int)? {
    var visited = Array(repeating: Array(repeating: false, count: N), count: N)
    var q: [(Int, Int, Int)] = []
    var head = 0
    q.append((sr, sc, 0))
    visited[sr][sc] = true

    var minDist = Int.max
    var target: (Int, Int)? = nil

    while head &amp;lt; q.count {
        let (r, c, d) = q[head]; head += 1
        if d &amp;gt; minDist { break } // 더 먼 곳은 볼 필요 없음

        // 먹이 후보
        if board[r][c] &amp;gt; 0 &amp;amp;&amp;amp; board[r][c] &amp;lt; size {
            if d &amp;lt; minDist {
                minDist = d
                target = (r, c)
            } else if d == minDist {
                // 위 &amp;rarr; 왼쪽 우선
                if let t = target {
                    if r &amp;lt; t.0 || (r == t.0 &amp;amp;&amp;amp; c &amp;lt; t.1) {
                        target = (r, c)
                    }
                }
            }
        }

        // 인접 확장
        for k in 0..&amp;lt;4 {
            let nr = r + dr[k], nc = c + dc[k]
            guard 0 &amp;lt;= nr, nr &amp;lt; N, 0 &amp;lt;= nc, nc &amp;lt; N else { continue }
            if visited[nr][nc] { continue }
            // 통과 가능 조건: 칸의 값 &amp;lt;= 상어 크기
            if board[nr][nc] &amp;lt;= size {
                visited[nr][nc] = true
                q.append((nr, nc, d + 1))
            }
        }
    }

    if let t = target {
        return (t.0, t.1, minDist)
    } else {
        return nil
    }
}

var size = 2
var eaten = 0
var time = 0

while true {
    if let (tr, tc, dist) = bfs(sharkR, sharkC, size) {
        time += dist
        sharkR = tr; sharkC = tc
        board[tr][tc] = 0
        eaten += 1
        if eaten == size {
            size += 1
            eaten = 0
        }
    } else {
        break
    }
}

print(time)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>BFS</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/119</guid>
      <comments>https://luckypaants.tistory.com/119#entry119comment</comments>
      <pubDate>Wed, 22 Oct 2025 22:47:32 +0900</pubDate>
    </item>
    <item>
      <title>[GithubAction] CD - feat.fastlane (3)</title>
      <link>https://luckypaants.tistory.com/118</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vfrpy/dJMb9YwtVd0/eCPgWpAevwKHwuEoOKGcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vfrpy/dJMb9YwtVd0/eCPgWpAevwKHwuEoOKGcM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vfrpy/dJMb9YwtVd0/eCPgWpAevwKHwuEoOKGcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVfrpy%2FdJMb9YwtVd0%2FeCPgWpAevwKHwuEoOKGcM0%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;318&quot; height=&quot;159&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CD(Continuous Deployment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적 배포라는 뜻으로, 빌드, 아카이빙, 업로드를 수동으로 하지 않고, 스크립트를 이용하여 손쉽게 자동화하여 배포하는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 &lt;b&gt;Fastlane&lt;/b&gt;과 &lt;b&gt;GithubAction&lt;/b&gt;을 이용하여 Testflight 및 앱스토어 배포 자동화 경험을 이야기해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Overview&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;match를 이용한 인증서 및 Provisining 관리&lt;/li&gt;
&lt;li&gt;fastlane을 이용한 Testflight 배포 자동화&lt;/li&gt;
&lt;li&gt;GithubAction을 이용하여 앱스토어 배포 원격 자동화 + Tag Push를 이용한 버전 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증서와 Provisioning 관리 - Fastlane match&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 프로젝트에서 위젯이 추가되면서, 개인 인증서 만으로는 개발 및 테스트하기 한계가 왔고, 팀원 1명의 계정을 이용하기로 했습니다. provisioning 파일과 인증서를 파일로 공유하여 관리하였는데 &lt;b&gt;번들id 변경, 디바이스&lt;/b&gt; 추가 또는 교체가 있을 때 마다 재발급하여 공유하는 것이 번거로웠습니다. 전에 진행했던 프로젝트에서 &lt;b&gt;fastlane match&lt;/b&gt;를 이용하였었는데 이번에도 적용하여 인증서와 provisioning &lt;b&gt;관리에 대한 부담&lt;/b&gt;을 줄였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Testflight 배포 자동화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;local&lt;/b&gt;에서 testflight 배포는 비교적 간단했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;match appstore 인증서 갱신&lt;/li&gt;
&lt;li&gt;API Key 세팅&lt;/li&gt;
&lt;li&gt;빌드 버전 가져오기&lt;/li&gt;
&lt;li&gt;빌드&lt;/li&gt;
&lt;li&gt;업로드&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;빌드 및 업로드 실패&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패에는 크게 2가지 원인이 있었는데요. 첫번째로, &lt;b&gt;프로비저닝 매칭&lt;/b&gt; 문제 두번째로 버전 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로비저닝이 1개 이상일 경우, 타겟이 제대로 찾지 못하는 문제가 발생하였습니다. 다음과 같이 명시적으로 지정하여 해결하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760971512417&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;build_app(
  scheme: &quot;iCo&quot;,
  clean: true,
  export_method: &quot;app-store&quot;,
  export_options: {
    signingStyle: &quot;manual&quot;,
    provisioningProfiles: {
      &quot;com.est.ico&quot; =&amp;gt; &quot;match AppStore com.est.ico&quot;,
      &quot;com.est.ico.widget&quot; =&amp;gt; &quot;match AppStore com.est.ico.widget&quot;
    }
  }
)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 버전을 가져와서 해결하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760971631935&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;latest = latest_testflight_build_number(
  app_identifier: &quot;com.est.ico&quot;,
  version: version
)

increment_build_number(
  build_number: latest + 1
)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;GithubAction's runner 환경에서 트러블 슈팅&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;로컬에서 쉽게 되어 workflow 적용을 금방될 줄 알았는데 생각보다 오래걸렸고 다음과 같은 고민도 해보게 되었습니다.&lt;/span&gt;&lt;span&gt;appstore 업로드를 분리하게 되면서, testflight 업로드를 언제 해야할지 고민하게 되었습니다. 매 dev 코드가 합쳐질 때 마다 testflight 업로드한다면, 너무 빈번한 업로드가 될 거 같았습니다. 회의 결과 dev와 release(main) 사이에 다른 하나의 레이어를 추가해보기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AppStoreConnect Key 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 파일 형태로 AppStoreConnect 인증서를 사용하고, path를 지정해주었었는데, runner에서는 파일 관리가 부담되었습니다. 다른 workflow를 참조해보니 key content를 문자열 형태로 사용하여, &lt;b&gt;파일 관리에 대한 부담&lt;/b&gt;을 줄였습니다. \n 문자열을 인식하지 못하여 &lt;b&gt;base64로 인코딩&lt;/b&gt;하여 사용하였습니다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;키체인 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러너 환경에서는 매 빌드마다 클린한 환경이고, &lt;b&gt;키 관련 보안과 팝업 없이&lt;/b&gt; 컨트롤 하기 위하여 매번 키체인을 생성합니다. 이 키체인 생성을 로컬환겨에서 테스트하다가 &lt;b&gt;기본 로그인 키체인이 변경&lt;/b&gt;되어 xcode에서 인증서를 인식하지 못하는 등의 문제가 발생하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cli에서 키체인을 삭제하고 재부팅하여 해결하였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인증서 저장소 접근&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;workflow에서 다른 저장소 접근하는 것은 action@checkut을 사용하여 괜찮았지만 lane 스크립트에서 저장소 접근할 때는 까다로웠습니다. git url의 token@github.com 패턴을 사용하여 url에 토큰을 삽입하여 접근하였습니다.&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;color: #ef5369;&quot;&gt;&lt;b&gt;로그가 찍힐 때 키가 노출될 위험&lt;/b&gt;&lt;/span&gt;이 있어, fastlane 공식 문서에서 추천하는 방법인 토큰을 base64로 인코딩하여 &lt;b&gt;authorization header&lt;/b&gt; 삽입으로 변경하였습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Workflow Step Working Directory&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;root가 xcodeproj에 위치하지 않아서 따로 지정해줘야했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1760974314372&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- name: execute lane
      working-directory: AIProject
      run: fastlane testflight
      env:
      ....&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&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;버전 태그를 push하면 해당 &lt;b&gt;이벤트를 트리거&lt;/b&gt;로 appstore upload를 수행하도록 workflow를 작성하였습니다.&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;workflow 전체&lt;/p&gt;
&lt;pre id=&quot;code_1760974543966&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: Release

on:
  push:
    tags:
      - &quot;Release-*&quot;

jobs:
  release:

    runs-on: macos-latest

    steps:
    # 레파지토리 체크인
    - name: Checkout
      uses: actions/checkout@v4

    # 태그 번호 가져오기
    - name: Extract version from tag
      id: ver
      run: echo &quot;VERSION=${GITHUB_REF_NAME#Release-}&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT

    # 태그 번호 가져오기
    - name: Print version
      run: echo &quot;Version is ${{ steps.ver.outputs.VERSION }}&quot;

    # XCConfig / GoogleService 등의 파일을 클론
    - name: Clone Secret file
      uses: actions/checkout@v4
      with:
        repository: ESTiOSAI/Secrets
        path: temp/
        token: ${{ secrets.SECRET_TOKEN }}
    
    # 가져온 파일 이동
    - name: Move config
      run: |
        mv temp/XCConfig/Secrets.xcconfig AIProject/iCo/App/Resource/
        mv temp/GoogleServices/GoogleService-Info.plist AIProject/iCo/App/Resource/

    - name: Configure git auth for match repo
      run: git config --global url.&quot;https://${GIT_TOKEN}@github.com/&quot;.insteadOf &quot;https://github.com/&quot;
      env:
        GIT_TOKEN: ${{ secrets.GIT_TOKEN }}

    - name: install fastlane
      run: brew install fastlane
      
    - name: execute lane
      working-directory: AIProject
      run: fastlane release version:${{ steps.ver.outputs.VERSION }}
      env:
        MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
        MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
        GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
        APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
        APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
        APP_STORE_CONNECT_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_KEY_CONTENT }}
        KEYCHAIN_NAME: ${{ secrets.KEYCHAIN_NAME }}
        KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고 및 Next Step&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말과 늦은 시간까지 투자해서 배포 자동화를 경험해본 뜻 깊은 시간이었습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;열정이 있는 팀원들이 있어서 즐겁게 해볼 수 있었고, 같이 개발하는 팀원이 중요하다는 것을 깨닫는 시간이었습니다.&lt;/span&gt; &lt;u&gt;githubAction&lt;/u&gt;까지 경험해본 것은 처음이라 예상한 시간보다 오래걸렸지만, xcode build 부터 깊게 알아봤던 의미있는 시간이었습니다.&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;다음은 fastlane에 의존적인 부분을 덜어내고 github marketplace의 apple-action을 이용하여 작성해보겠습니다.&lt;/p&gt;</description>
      <category>iOS/자동화</category>
      <category>cd</category>
      <category>Fastlane</category>
      <category>githubAction</category>
      <category>Match</category>
      <category>TestFlight</category>
      <category>workflow</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/118</guid>
      <comments>https://luckypaants.tistory.com/118#entry118comment</comments>
      <pubDate>Tue, 21 Oct 2025 00:44:22 +0900</pubDate>
    </item>
    <item>
      <title>[코딩테스트]   전깃줄 - 백준 2565</title>
      <link>https://luckypaants.tistory.com/117</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2565&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문제링크&lt;/a&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;난이도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;골드 5&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP, LIS&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소요시간&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1시간&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP 문제도 이번 챌린지 들어서 처음이고, LIS 알고리즘도 몰라서 어려웠다. 인접 배열간에 수열이 만들어지면 카운트를 했는데, 인접 배열이 아닌, 전체 배열에서 증가 수열을 구해야했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드&lt;/h3&gt;
&lt;pre class=&quot;swift&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;let N = Int(readLine()!)!

var graph = Array(repeating: 0, count: 501)

(0..&amp;lt;N).forEach { _ in
    let row = readLine()!.split(separator: &quot; &quot;).map { Int($0)! }
    graph[row[0]] = row[1]
}

let dest = graph
    .enumerated()
    .filter { $0.element != 0 }
    .sorted { $0.0 &amp;lt; $1.0 }
    .map { ($0.element) }

var dp = Array(repeating: 1, count: dest.count)

print(dest)

for i in 0..&amp;lt;dest.count {
    for j in 0..&amp;lt;i {
        if dest[i] &amp;gt; dest[j] {
            dp[i] = max(dp[i], dp[j] + 1)
        }
    }
}

print(N - dp.max()!)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>알고리즘</category>
      <category>dp</category>
      <category>Lis</category>
      <author>운좋은바지</author>
      <guid isPermaLink="true">https://luckypaants.tistory.com/117</guid>
      <comments>https://luckypaants.tistory.com/117#entry117comment</comments>
      <pubDate>Mon, 20 Oct 2025 23:06:04 +0900</pubDate>
    </item>
  </channel>
</rss>