<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>이리메라 갖다가</title>
    <link>https://ahrzosel.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 18:50:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>너이르나</managingEditor>
    <image>
      <title>이리메라 갖다가</title>
      <url>https://tistory1.daumcdn.net/tistory/6423377/attach/020127a647824302b87514e3805ca108</url>
      <link>https://ahrzosel.tistory.com</link>
    </image>
    <item>
      <title>8주차 WIL</title>
      <link>https://ahrzosel.tistory.com/50</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FACTS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;프로젝트명 : 메모앱 만들기 심화&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FINDINGS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;table style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #666666; text-align: center; width: 12.674419%;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #666666; width: 87.209302%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;스토리보드 없이 코드로만 작업할 때 메인 화면 로드 불가&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;popVC 화면 전환 불가&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;오토레이아웃 등의 중복 코드 작성으로 인한 시간 소모&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Array를 &lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;UserDefaults로&lt;/span&gt;&amp;nbsp;저장하려고 했는데 에러 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #666666; text-align: center; width: 12.674419%;&quot;&gt;&lt;b&gt;해결방안&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #666666; width: 87.209302%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;SceneDelegate 파일에서 초기 뷰컨트롤러 지정&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;네비게이션 컨트롤러를 루트 컨트롤러 앞에 지정&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;코드스니펫 생성&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;UserDefaults는 기본 데이터 형식만 저장할 수 있어서 모델에 Codable 프로토콜 채택&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FUTURE&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;UserDefaults 공식문서 확인&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;caret-color: #555555;&quot;&gt;코드로 앱 구현하는데 익숙해지기&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;문제 발생 시 디버깅이나 프린트문으로 원인 찾기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FEELINGS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;코드로 작업하는게 재밌다! 좀 더 이해가 잘 되는 듯?&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;반복하면 반복할수록 머리에 잘 들어온다! 반복만이 살길&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;주말에도 공부하자... 아직 부족해ㅠ&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WIL</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/50</guid>
      <comments>https://ahrzosel.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 4 Sep 2023 21:37:01 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] Youtube API 활용하여 앱 만들기 (1) : Youtube API 사용방법</title>
      <link>https://ahrzosel.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Youtube API 사용 방법에 대해 정리해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;유튜브 가이드&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/youtube/v3/docs?hl=ko&quot;&gt;https://developers.google.com/youtube/v3/docs?hl=ko&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1693826985955&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;API Reference &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English API Reference 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. YouTube Data API를 사용하면 YouTube&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/youtube/v3/docs?hl=ko&quot; data-og-url=&quot;https://developers.google.com/youtube/v3/docs?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developers.google.com/youtube/v3/docs?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/youtube/v3/docs?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;API Reference &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English API Reference 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. YouTube Data API를 사용하면 YouTube&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;API key 발급&lt;/b&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Developer Console 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://console.developers.google.com/?hl=ko&quot;&gt;https://console.developers.google.com/?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693827116457&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;Google 클라우드 플랫폼&quot; data-og-description=&quot;로그인 Google 클라우드 플랫폼으로 이동&quot; data-og-host=&quot;accounts.google.com&quot; data-og-source-url=&quot;https://console.developers.google.com/?hl=ko&quot; data-og-url=&quot;https://accounts.google.com/v3/signin/identifier?continue=https%3A%2F%2Fconsole.cloud.google.com%2Fapis%2Fdashboard%3Fhl%3Dko&amp;amp;followup=https%3A%2F%2Fconsole.cloud.google.com%2Fapis%2Fdashboard%3Fhl%3Dko&amp;amp;hl=ko&amp;amp;ifkv=AXo7B7XKr6xLG1b_azb5CobVbxUhz9QI4GgP26OHSPJXyXzEgumvqg8F7fbpJegzlxIy8LUc2l11&amp;amp;osid=1&amp;amp;passive=1209600&amp;amp;service=cloudconsole&amp;amp;flowName=WebLiteSignIn&amp;amp;flowEntry=ServiceLogin&amp;amp;dsh=S-1342130037%3A1693827115041927&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://console.developers.google.com/?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://console.developers.google.com/?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Google 클라우드 플랫폼&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;로그인 Google 클라우드 플랫폼으로 이동&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;accounts.google.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;h4 data-ke-size=&quot;size20&quot;&gt;2. 라이브러리 - YouTube Data API v3 선택&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.34.42.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xpx83/btstaBRxPXM/djmWdRddMxXuwQSokKp2kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xpx83/btstaBRxPXM/djmWdRddMxXuwQSokKp2kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xpx83/btstaBRxPXM/djmWdRddMxXuwQSokKp2kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxpx83%2FbtstaBRxPXM%2FdjmWdRddMxXuwQSokKp2kk%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;2870&quot; height=&quot;1386&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.34.42.png&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1386&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;h4 data-ke-size=&quot;size20&quot;&gt;3. API key 생성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxjmhA/btss3YG1cKK/it8GvkqR5AXIQ2nA5GjY20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxjmhA/btss3YG1cKK/it8GvkqR5AXIQ2nA5GjY20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxjmhA/btss3YG1cKK/it8GvkqR5AXIQ2nA5GjY20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxjmhA%2Fbtss3YG1cKK%2Fit8GvkqR5AXIQ2nA5GjY20%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;1436&quot; height=&quot;636&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.39.50.png&quot; data-origin-width=&quot;2868&quot; data-origin-height=&quot;1334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l9Tzk/btssVOdOqQP/AVvDjZ88xDURNE6BJKVoHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l9Tzk/btssVOdOqQP/AVvDjZ88xDURNE6BJKVoHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l9Tzk/btssVOdOqQP/AVvDjZ88xDURNE6BJKVoHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl9Tzk%2FbtssVOdOqQP%2FAVvDjZ88xDURNE6BJKVoHK%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;2868&quot; height=&quot;1334&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.39.50.png&quot; data-origin-width=&quot;2868&quot; data-origin-height=&quot;1334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.41.09.png&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;1160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbg3QQ/btssVLVEPH4/gmMIbSUUKB1PkNaWnd6su0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbg3QQ/btssVLVEPH4/gmMIbSUUKB1PkNaWnd6su0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbg3QQ/btssVLVEPH4/gmMIbSUUKB1PkNaWnd6su0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbg3QQ%2FbtssVLVEPH4%2FgmMIbSUUKB1PkNaWnd6su0%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;2002&quot; height=&quot;1160&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.41.09.png&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;1160&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;API 사용&lt;/b&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 검색&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청: HTTP 요청&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GET https://www.googleapis.com/youtube/v3/search&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필수 매개변수: part(string)&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;part&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;매개변수는 API 응답이 포함하는&amp;nbsp;&lt;/span&gt;search&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;리소스 속성 하나 이상의 쉼표로 구분된 목록을 지정한다. (&lt;/span&gt;snippet&lt;span style=&quot;text-align: left;&quot;&gt;로 설정)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;&quot;&gt;선택적 매개변수&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;&quot;&gt;maxResults(integer)&lt;br /&gt;: &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;결과 집합에 반환해야 하는 최대 항목 수를 지정한다. 사용 가능한 값:&amp;nbsp;&lt;/span&gt;0&lt;span style=&quot;text-align: left;&quot;&gt;~&lt;/span&gt;50,&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;기본값은&amp;nbsp;&lt;/span&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;q(string)&lt;br /&gt;: 검색할 검색어를 지정한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;&quot;&gt;그밖의 매개변수는 &lt;a href=&quot;https://developers.google.com/youtube/v3/docs/search/list?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 홈페이지 참조&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1693828117555&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;Search: list &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English Search: list 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. API 요청에 지정된 쿼리 매개변&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/youtube/v3/docs/search/list?hl=ko&quot; data-og-url=&quot;https://developers.google.com/youtube/v3/docs/search/list?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developers.google.com/youtube/v3/docs/search/list?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/youtube/v3/docs/search/list?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Search: list &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English Search: list 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. API 요청에 지정된 쿼리 매개변&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.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&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/Y7oaA/btssTsCC2fc/ykE9vF3qPCVz1b5wZMfKBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y7oaA/btssTsCC2fc/ykE9vF3qPCVz1b5wZMfKBk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1904&quot; data-origin-height=&quot;1406&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.50.06.png&quot; data-widthpercent=&quot;49.27&quot; style=&quot;width: 48.696358%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y7oaA/btssTsCC2fc/ykE9vF3qPCVz1b5wZMfKBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY7oaA%2FbtssTsCC2fc%2FykE9vF3qPCVz1b5wZMfKBk%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;1904&quot; height=&quot;1406&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1U4HK/btssYSAemOS/cmKFw32DTGRj4AP8qpq281/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1U4HK/btssYSAemOS/cmKFw32DTGRj4AP8qpq281/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;284&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.50.46.png&quot; data-widthpercent=&quot;50.73&quot; style=&quot;width: 50.140851%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1U4HK/btssYSAemOS/cmKFw32DTGRj4AP8qpq281/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1U4HK%2FbtssYSAemOS%2FcmKFw32DTGRj4AP8qpq281%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;396&quot; height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.51.16.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;1302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmaDqv/btssZdkbBFf/S11worjfa32AQVxKjHelo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmaDqv/btssZdkbBFf/S11worjfa32AQVxKjHelo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmaDqv/btssZdkbBFf/S11worjfa32AQVxKjHelo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmaDqv%2FbtssZdkbBFf%2FS11worjfa32AQVxKjHelo1%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;2288&quot; height=&quot;1302&quot; data-filename=&quot;스크린샷 2023-09-04 오후 8.51.16.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;1302&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;h4 data-ke-size=&quot;size20&quot;&gt;2. 동영상&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청: HTTP 요청&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;GET https://www.googleapis.com/youtube/v3/videos&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필수 매개변수: part(string)&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;part&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;매개변수는 API 응답이 포함하는&amp;nbsp;&lt;/span&gt;video&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;리소스 속성 하나 이상의 쉼표로 구분된 목록을 지정한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;매개변수가 하위 속성을 포함하는 속성을 식별하는 경우 하위 속성이 응답에 포함된다. 예를 들어 &lt;/span&gt;video&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;리소스에서&amp;nbsp;&lt;/span&gt;snippet&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;속성에는&amp;nbsp;&lt;/span&gt;channelId&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;title&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;description&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;tags&lt;span style=&quot;text-align: left;&quot;&gt;,&amp;nbsp;&lt;/span&gt;categoryId&lt;span style=&quot;text-align: left;&quot;&gt; 속성이 포함되어 있다. 따라서 &lt;/span&gt;part=snippet&lt;span style=&quot;text-align: left;&quot;&gt;를 설정하면 API 응답에 이러한 모든 속성이 포함된다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;다음 목록에는 매개변수 값에 포함할 수 있는&amp;nbsp;&lt;/span&gt;part&lt;span style=&quot;text-align: left;&quot;&gt; 이름이 포함되어 있습니다.&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;contentDetails&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;fileDetails&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;id&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;liveStreamingDetails&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;localizations&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;player&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;processingDetails&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;recordingDetails&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;snippet&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;statistics&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;status&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;suggestions&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;topicDetails&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;필터: chart(string)&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;chart&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;매개변수는 검색하려는 차트를 식별한다.&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;mostPopular&amp;nbsp;&amp;ndash; 지정된&amp;nbsp;콘텐츠 지역&amp;nbsp;및&amp;nbsp;동영상 카테고리에 대해 가장 인기 있는 동영상을 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;선택적 매개변수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;maxResults(integer)&lt;br /&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;결과 집합에 반환해야 하는 최대 항목 수를 지정한다. 사용 가능한 값:&amp;nbsp;&lt;/span&gt;0&lt;span style=&quot;text-align: left;&quot;&gt;~&lt;/span&gt;50,&lt;span style=&quot;text-align: left;&quot;&gt;&amp;nbsp;기본값은&amp;nbsp;&lt;/span&gt;5&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;그밖의 매개변수는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://developers.google.com/youtube/v3/docs/videos/list?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 홈페이지 참조&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1693828400706&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;Videos: list &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English Videos: list 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. API 요청 매개변수와 일치하는 &quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/youtube/v3/docs/videos/list?hl=ko&quot; data-og-url=&quot;https://developers.google.com/youtube/v3/docs/videos/list?hl=ko&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developers.google.com/youtube/v3/docs/videos/list?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/youtube/v3/docs/videos/list?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Videos: list &amp;nbsp;|&amp;nbsp; YouTube Data API &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English Videos: list 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. API 요청 매개변수와 일치하는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.01.11.png&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;1406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xoYEo/btss3LgvwNu/nU3JkbnMSKCBv1ukC0SM2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xoYEo/btss3LgvwNu/nU3JkbnMSKCBv1ukC0SM2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xoYEo/btss3LgvwNu/nU3JkbnMSKCBv1ukC0SM2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxoYEo%2Fbtss3LgvwNu%2FnU3JkbnMSKCBv1ukC0SM2k%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;1902&quot; height=&quot;1406&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.01.11.png&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;1406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.01.45.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;1294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJBIp3/btss3FHndx7/hSNkmNcEl5EX12DN3NETyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJBIp3/btss3FHndx7/hSNkmNcEl5EX12DN3NETyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJBIp3/btss3FHndx7/hSNkmNcEl5EX12DN3NETyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJBIp3%2Fbtss3FHndx7%2FhSNkmNcEl5EX12DN3NETyK%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;2288&quot; height=&quot;1294&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.01.45.png&quot; data-origin-width=&quot;2288&quot; data-origin-height=&quot;1294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/49</guid>
      <comments>https://ahrzosel.tistory.com/49#entry49comment</comments>
      <pubDate>Mon, 4 Sep 2023 21:02:21 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (8) : TableView 살펴보기</title>
      <link>https://ahrzosel.tistory.com/48</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;beginUpdates(), endUpdates()&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UITableView&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;를 업데이트할 때 사용되는 메서드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;- 테이블 뷰의 데이터 소스를 변경할 때 테이블 뷰의 셀들을 새로고침하고 애니메이션 효과를 부여하는데 사용됨&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151; 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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;사용 장점&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애니메이션 및 레이아웃 최적화&lt;/b&gt;: 테이블 뷰의 셀들이 추가되거나 삭제될 때, 애니메이션 효과와 함께 레이아웃이 변경되는 경우가 있는데 beginUpdates()와 endUpdates()를 사용하면 셀들의 추가 및 삭제에 대한 애니메이션 효과가 부드럽게 적용할 수 있으며, 테이블 뷰의 레이아웃 최적화가 이루어진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 일관성 유지&lt;/b&gt;: beginUpdates()와 endUpdates()를 사용하면 테이블 뷰의 데이터 소스를 변경하는 과정에서 테이블 뷰와 데이터 소스 간의 일관성이 유지되며, 이를 통해 예상치 못한 동작이나 오류를 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시 업데이트 처리&lt;/b&gt;: 여러 셀을 동시에 추가하거나 삭제하는 경우에도 beginUpdates()와 endUpdates()를 사용하면 테이블 뷰가 한 번에 하나씩 셀을 업데이트하지 않고, 모든 변경 사항을 한 번에 처리할 수 있어 성능이 향상된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배치 업데이트 지원&lt;/b&gt;: 테이블 뷰의 insertRows, deleteRows, insertSections, deleteSections 등의 메서드를 사용하여 여러 셀이나 섹션을 한 번에 추가하거나 삭제할 수 있다. 이러한 배치 업데이트 작업을 beginUpdates()와 endUpdates() 내에서 수행하면 애니메이션 효과와 함께 셀들이 한 번에 업데이트되며, 사용자에게 보다 나은 사용자 경험을 제공할 수 있다.&lt;/li&gt;
&lt;/ul&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;/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/bGpypn/btssTfPwihz/173ah8dSRWm5UG6rpxpDuk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGpypn/btssTfPwihz/173ah8dSRWm5UG6rpxpDuk/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone 14 Pro - 2023-09-01 at 19.52.51.gif&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/bGpypn/btssTfPwihz/173ah8dSRWm5UG6rpxpDuk/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGpypn%2FbtssTfPwihz%2F173ah8dSRWm5UG6rpxpDuk%2Fimg.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;295&quot; height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qd0CC/btssUby6ES4/bj6DtExwklRZF6GpYCkkT0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qd0CC/btssUby6ES4/bj6DtExwklRZF6GpYCkkT0/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone 14 Pro - 2023-09-01 at 19.53.43.gif&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qd0CC/btssUby6ES4/bj6DtExwklRZF6GpYCkkT0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqd0CC%2FbtssUby6ES4%2Fbj6DtExwklRZF6GpYCkkT0%2Fimg.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;295&quot; height=&quot;640&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;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;</description>
      <category>TIL</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/48</guid>
      <comments>https://ahrzosel.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 1 Sep 2023 19:56:34 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (7) : UserDefaults 로 데이터 저장하기</title>
      <link>https://ahrzosel.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스토리보드 없이 메모앱 만들기를 진행하고 있는데, Todo에 해당하는 프로퍼티를 최대한 간단하게 정의하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1693477459853&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Todo {
    var todo: String
    var isCompleted: Bool
}&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;기초부터 차근차근 해보려고 todo 내용과 완료여부만 정의했고, Todo를 관리할 Manager를 구조체로 정의하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1693477573406&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct TodoManager {
    static let userDefaults = UserDefaults.standard
    
    // MARK: - Variables
    static var todoList: [Todo] = [
        Todo(todo: &quot;킬링보이스 악뮤 보기&quot;, isCompleted: true),
        Todo(todo: &quot;개인 과제 코드로만 해보기&quot;, isCompleted: false)
    ]
    
    // MARK: - Load
    static func loadTodo() {
        
    }
    
    // MARK: - Add
    static func addTodo(_ newTodo: Todo) {
        var updatedTodoList = todoList
        updatedTodoList.append(newTodo)
        todoList = updatedTodoList
        print(todoList)
    }
    
    // MARK: - Save
    static func saveTodo(_ saveTodo: Todo) {
        
    }
    
    // MARK: - Delete
    
}&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;UserDefaults를 이용해서 간단하게 저장과 읽어오기를 구현하기 위해 addTodo 함수에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래와 같은 코드를 추가했는데 오류가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1693477667487&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;userDefaults.set(todoList, forKey: &quot;todoList&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&amp;nbsp;ERROR: Attempt to insert non-property list object&amp;nbsp;&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;아니 이게 뭔데...&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무나 당연하게 입력받는 Todo를 append 하듯이 set에 value로 넣었는데 UserDefaults는 기본 데이터 형식(property list)만 저장할 수 있다고 안된단다...&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;그렇다 UserDefaults는 Array나 Dictionary와 같은 복잡한 데이터 구조를 직접 저장하지 못하는 문제가 있는 것이다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 내가 의도한 것과 같이 배열의 형태로 저장하고 싶으면&lt;span style=&quot;color: #333333;&quot;&gt; &amp;nbsp;&lt;u&gt;&lt;b&gt;Codable 프로토콜&lt;/b&gt;&lt;/u&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;그치만, Codable 프로토콜을 채택하지 않고 프로퍼티를 개별로 저장하면 될 것 같아서 아래와 같이 수정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1693477983270&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;userDefaults.set(newTodo.todo, forKey: &quot;todoTitle&quot;)
userDefaults.set(newTodo.isCompleted, forKey: &quot;isCompleted&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 입력받는 Todo를 newTodo라고 정의하고 각 프로퍼티들을 별도로 저장해준다.&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;프로퍼티들을 개별로 저장할 수는 있으나, 나중에 todoList를 load 할 때 이 데이터들을 다시 불러와서 Todo 객체로 조합해야 하는 것이다. 쉽게 말해서 또 작업이 추가된다는 뜻이다.&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;이렇게 분리된 저장 방식은 데이터를 관리하거나 활용하기에 복잡해져서 객체를 한번에 저장하거나 로드하기 위해서는 Codable 프로토콜을 사용하는 것이 더 효율적이다.&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;하지만 무시하고 loadTodo를 짰다.&lt;/p&gt;
&lt;pre id=&quot;code_1693478253791&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static func loadTodo() {
    if let todo = userDefaults.string(forKey: &quot;todoTitle&quot;),
       let isCompleted = userDefaults.bool(forKey: &quot;isCompleted&quot;) {
       	// 로드될 todoList를 정의하려고 했으나...
        let loadTodoList = Todo(todo: todo, isCompleted: isCompleted)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 에러 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&amp;nbsp;ERROR: Initializer for conditional binding must have Optional type, not 'Bool'&amp;nbsp;&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;이 에러가 무엇인고 하니,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userDefaults.bool(forKey:) 메서드는 Bool 값을 반환하며, 이 값은 옵셔널이 아니기때문에 조건부 바인딩으로 추출할 수 없다는 말이다...&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;h4 data-ke-size=&quot;size20&quot;&gt;저장&lt;/h4&gt;
&lt;pre id=&quot;code_1693479067262&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 여러 개의 개별 값들을 저장(순서를 기억해야 함)
userDefaults.set(newTodo.todo, forKey: &quot;todoTitle\(todoList.count)&quot;)
userDefaults.set(newTodo.isCompleted, forKey: &quot;isCompleted\(todoList.count)&quot;)

// todoList 배열의 길이 갱신
let newCount = todoList.count + 1
userDefaults.set(newCount, forKey: &quot;todoListCount&quot;)

// 로컬 배열 갱신
todoList.append(newTodo)&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;불러오기&lt;/h4&gt;
&lt;pre id=&quot;code_1693479134943&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// todoList 배열 불러오기
if let count = userDefaults.value(forKey: &quot;todoListCount&quot;) as? Int {
    for index in 0..&amp;lt;count {
        if let todo = userDefaults.string(forKey: &quot;todoTitle\(index)&quot;),
           let isCompleted = userDefaults.bool(forKey: &quot;isCompleted\(index)&quot;) {
            let loadedTodo = Todo(todo: todo, isCompleted: isCompleted)
            todoList.append(loadedTodo)
        }
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;..... Codable 프로토콜을 채택했다. 진작에 할껄.. 너무 쉽게 저장되네&lt;/p&gt;
&lt;pre id=&quot;code_1693478506233&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Load
static func loadTodo() {
    if let data = userDefaults.data(forKey: &quot;todoList&quot;),
       let loadTodoList = try? JSONDecoder().decode([Todo].self, from: data) {
        todoList = loadTodoList
    }
}

// MARK: - Add
static func addTodo(_ newTodo: Todo) {
    var updatedTodoList = todoList
    updatedTodoList.append(newTodo)
    todoList = updatedTodoList
    saveTodo(newTodo)
    loadTodo()
}

// MARK: - Save
static func saveTodo(_ saveTodo: Todo) {
    if let saveData = try? JSONEncoder().encode(todoList) {
        userDefaults.set(saveData, forKey: &quot;todoList&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 저장하는 부분은 saveTodo 함수안에 구현하여 저장하고, loadTodo 함수에 저장된 데이터를 디코딩해서 불러온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 todo를 추가할 때나 삭제할 때 해당 로직을 작성하고 saveTodo()만 호출해주면 너무나 쉽게 todo를 저장할 수 있다.&lt;/p&gt;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/47</guid>
      <comments>https://ahrzosel.tistory.com/47#entry47comment</comments>
      <pubDate>Thu, 31 Aug 2023 19:47:00 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (6) : 스토리보드 없이 코드로 개발하기 초기 셋팅, 코드스니펫 만들기</title>
      <link>https://ahrzosel.tistory.com/46</link>
      <description>&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;h3 data-ke-size=&quot;size23&quot;&gt;Storyborad 흔적 지우기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Main.storyboard 삭제&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.35.54.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PHkZf/btssHgtMoma/gYi7vyuUd4xqHGzl2ln8W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PHkZf/btssHgtMoma/gYi7vyuUd4xqHGzl2ln8W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PHkZf/btssHgtMoma/gYi7vyuUd4xqHGzl2ln8W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPHkZf%2FbtssHgtMoma%2FgYi7vyuUd4xqHGzl2ln8W1%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;316&quot; height=&quot;265&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.35.54.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 만들고나면 생기는 기본 Main 스토리보드를 삭제한다.&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;Scene Configuration 삭제&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.38.00.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKSQLa/btssHyuiJ32/G8Wne0CKtpD2EzvgMYnSHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKSQLa/btssHyuiJ32/G8Wne0CKtpD2EzvgMYnSHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKSQLa/btssHyuiJ32/G8Wne0CKtpD2EzvgMYnSHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKSQLa%2FbtssHyuiJ32%2FG8Wne0CKtpD2EzvgMYnSHK%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;660&quot; height=&quot;294&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.38.00.png&quot; data-origin-width=&quot;844&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내 Info -&amp;gt; Information Property List -&amp;gt; Application Scene Manifes -&amp;gt; Scene Configuration -&amp;gt; Application Session Role -&amp;gt; Item 0 경로를 타고 들어가서 Storyborad Name를 삭제한다.&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;Main storyboard file base name 삭제&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.40.24.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pM9Xz/btssvXCEyYo/LnRJsxKn84xSK4AYFckq3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pM9Xz/btssvXCEyYo/LnRJsxKn84xSK4AYFckq3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pM9Xz/btssvXCEyYo/LnRJsxKn84xSK4AYFckq3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpM9Xz%2FbtssvXCEyYo%2FLnRJsxKn84xSK4AYFckq3K%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;841&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.40.24.png&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 내 Info 에서 Main storyboard file base name을 삭제한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Initial View Controller 셋팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 프로젝트를 만들때 스토리보드에는 아래와 같이 ViewController가 있고 그 왼쪽에는 작은 화살표가 표시되는데, 그 화살표는 프로젝트를 빌드했을 때 처음 나타나는 화면을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.51.27.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N1CDq/btssAId6iNS/hlQXluMlWuNtyGoFBABNSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N1CDq/btssAId6iNS/hlQXluMlWuNtyGoFBABNSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N1CDq/btssAId6iNS/hlQXluMlWuNtyGoFBABNSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN1CDq%2FbtssAId6iNS%2FhlQXluMlWuNtyGoFBABNSk%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;1201&quot; height=&quot;770&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.51.27.png&quot; data-origin-width=&quot;1201&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 우리의 프로젝트는 스토리보드를 삭제했기 때문에 지금 상태로 빌드하면 까만 화면이 나와 ViewController에서 작업한 내용을 확인할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 초기 화면을 View Controller로 설정해주는 작업이 필요하다. 해당 작업은 그전엔 들여다보지 않았던 SceneDelegate에서 수행하게 된다.&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;파일을 열어보면 15번째 줄부터 20번째 줄 까지의 내용이 처음 프로젝트가 빌드될 때 보여지는 Scene에 대한 코드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.55.43.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGyR7r/btssxHNg1s7/nvmDK6OOC5msg6MaHd3Pb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGyR7r/btssxHNg1s7/nvmDK6OOC5msg6MaHd3Pb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGyR7r/btssxHNg1s7/nvmDK6OOC5msg6MaHd3Pb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGyR7r%2FbtssxHNg1s7%2FnvmDK6OOC5msg6MaHd3Pb1%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;899&quot; height=&quot;290&quot; data-filename=&quot;스크린샷 2023-08-30 오후 8.55.43.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;290&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;이제 프로젝트가 빌드될 때 처음 로드될 화면을 ViewController로 설정하는 코드를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693396672579&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = ViewController()
    self.window = window
    self.window?.makeKeyAndVisible()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;windowScene &lt;span&gt;변수&lt;/span&gt; &lt;span&gt;설정&lt;/span&gt;: &lt;span&gt;주어진&lt;/span&gt; scene &lt;span&gt;파라미터를&lt;/span&gt; UIWindowScene&lt;span&gt;으로&lt;/span&gt; &lt;span&gt;타입&lt;/span&gt; &lt;span&gt;캐스팅하여&lt;/span&gt; windowScene &lt;span&gt;변수에&lt;/span&gt; &lt;span&gt;할당&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;UIWindow &lt;span&gt;생성&lt;/span&gt;: UIWindow&lt;span&gt;를&lt;/span&gt; &lt;span&gt;생성하고&lt;/span&gt;, &lt;span&gt;방금&lt;/span&gt; &lt;span&gt;얻어온&lt;/span&gt; windowScene&lt;span&gt;을&lt;/span&gt; &lt;span&gt;사용하여&lt;/span&gt; &lt;span&gt;새로운&lt;/span&gt; &lt;span&gt;윈도우를&lt;/span&gt; &lt;span&gt;생성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;루트&lt;/span&gt; &lt;span&gt;뷰&lt;/span&gt; &lt;span&gt;컨트롤러&lt;/span&gt; &lt;span&gt;설정&lt;/span&gt;: &lt;span&gt;생성한&lt;/span&gt; &lt;span&gt;윈도우의&lt;/span&gt; rootViewController &lt;span&gt;속성에&lt;/span&gt; ViewController() &lt;span&gt;인스턴스를&lt;/span&gt; &lt;span&gt;할당.&lt;/span&gt;&amp;nbsp;&lt;span&gt;이렇게&lt;/span&gt; &lt;span&gt;하면&lt;/span&gt; &lt;span&gt;앱이&lt;/span&gt; &lt;span&gt;실행되었을&lt;/span&gt; &lt;span&gt;때&lt;/span&gt; &lt;span&gt;초기&lt;/span&gt; &lt;span&gt;화면에&lt;/span&gt; &lt;span&gt;표시될&lt;/span&gt; &lt;span&gt;뷰&lt;/span&gt; &lt;span&gt;컨트롤러가&lt;/span&gt; &lt;span&gt;설정됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;윈도우&lt;span&gt; &lt;/span&gt;키&lt;span&gt; &lt;/span&gt;설정&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;표시&lt;span&gt;: &lt;/span&gt;생성한&lt;span&gt; &lt;/span&gt;윈도우를&lt;span&gt; &lt;/span&gt;앱의&lt;span&gt; &lt;/span&gt;주요&lt;span&gt; &lt;/span&gt;윈도우로&lt;span&gt; &lt;/span&gt;설정하고&lt;span&gt;, makeKeyAndVisible() &lt;/span&gt;메서드를&lt;span&gt; &lt;/span&gt;호출하여&lt;span&gt; &lt;/span&gt;윈도우를&lt;span&gt; &lt;/span&gt;화면에&lt;span&gt; &lt;/span&gt;보이도록&lt;span&gt; &lt;/span&gt;설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 상기와 같이 ViewController에 바로 연결하게 되면 페이지가 늘어났을 때 네비게이션을 활용하여 화면 전환을 해야하는데 어려움이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음 로드되는 화면을 NavigationController로 설정하고 ViewController를 root로 연결하기 위해 아래와 같이 코드를 수정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1693396932969&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    window = UIWindow(windowScene: windowScene)
    let mainVC = UINavigationController(rootViewController: ViewController())
    self.window?.rootViewController = mainVC
    self.window?.makeKeyAndVisible()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트 뷰 컨트롤러 설정: UINavigationController의 인스턴스를 생성하고, 그 안에 ViewController() 인스턴스를 루트 뷰 컨트롤러로 설정. 이렇게 하면 앱이 실행되었을 때 네비게이션 컨트롤러를 루트로 하는 뷰 계층이 생성되며, ViewController()가 첫 화면으로 보여짐&lt;/li&gt;
&lt;li&gt;&lt;span&gt;윈도우의&lt;/span&gt; &lt;span&gt;루트&lt;/span&gt; &lt;span&gt;뷰&lt;/span&gt; &lt;span&gt;컨트롤러&lt;/span&gt; &lt;span&gt;설정&lt;/span&gt; &lt;span&gt;및&lt;/span&gt; &lt;span&gt;표시&lt;/span&gt;: &lt;span&gt;생성한&lt;/span&gt; &lt;span&gt;윈도우의&lt;/span&gt; rootViewController&lt;span&gt;를&lt;/span&gt; mainVC&lt;span&gt;로&lt;/span&gt; &lt;span&gt;설정하고&lt;/span&gt;, makeKeyAndVisible() &lt;span&gt;메서드를&lt;/span&gt; &lt;span&gt;호출하여&lt;/span&gt; &lt;span&gt;윈도우를&lt;/span&gt; &lt;span&gt;화면에&lt;/span&gt; &lt;span&gt;표시&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;생각보다 처음에 작성할 게 많아 번거로웠다. 확실히 스토리보드로 작업하게되면 이런 초기 셋팅을 하지 않아도 되니까 편하긴 한 것 같다. 그리고 스토리보드에서는 UI를 구현하는데 많은 시간이 투자되지 않고 코드 작업이 적어서 좋았다.&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;아 그리고, 코드로 작업하면서 반복되는 코드들을 code snippet을 이용해서 간단하게 사용할 수 있는 방법을 알게 되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Snippets&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 개발을 하게 되면서 초기 셋팅을 위한 Scene 설정이라든지, 기본적인 UI 셋팅이라던지 반복되는 코드들을 스니펫으로 저장하여 쉽게 쓸 수 있는 방법을 정리하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 저장된 코드 스니펫을 확인하고 편집, 삭제를 할 수 있는 단축키는 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&amp;nbsp;command + shift + L &lt;/span&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;아까와 같이 Scene을 변경하기 위한 코드를 예로 들자면, 코드 스니펫을 만들 코드를 블럭으로 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.13.01.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgN2TN/btssCs9r9P7/xP9yoSfGjeOWm4Am1LEQbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgN2TN/btssCs9r9P7/xP9yoSfGjeOWm4Am1LEQbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgN2TN/btssCs9r9P7/xP9yoSfGjeOWm4Am1LEQbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgN2TN%2FbtssCs9r9P7%2FxP9yoSfGjeOWm4Am1LEQbK%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;701&quot; height=&quot;518&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.13.01.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 블럭을 우클릭하게 되면 중간에 있는 Create Code Snippet...을 선택한다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.17.12.png&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blc53V/btssBtgtRKZ/BhCpzfwtUkYvDOj6sonhyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blc53V/btssBtgtRKZ/BhCpzfwtUkYvDOj6sonhyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blc53V/btssBtgtRKZ/BhCpzfwtUkYvDOj6sonhyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblc53V%2FbtssBtgtRKZ%2FBhCpzfwtUkYvDOj6sonhyK%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;777&quot; height=&quot;470&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.17.12.png&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;My Code Snippet이라고 적힌 부분은 저장한 코드 스니펫의 명칭이고, 중요한건 하단에 Completion이라고 있는 부분인데 쉽게 말해 키워드다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.21.32.png&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJfLr0/btssAJjJpZW/b4qt6ja5Bo0WKqSrxEl0Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJfLr0/btssAJjJpZW/b4qt6ja5Bo0WKqSrxEl0Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJfLr0/btssAJjJpZW/b4qt6ja5Bo0WKqSrxEl0Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJfLr0%2FbtssAJjJpZW%2Fb4qt6ja5Bo0WKqSrxEl0Dk%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;381&quot; height=&quot;190&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.21.32.png&quot; data-origin-width=&quot;381&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 알아보기 쉽게 naviWindowScene 이라고 설정했고, naviWi까지만 쳤는데도 자동완성 되는 모습을 볼 수 있다.&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;나는 뷰컨트롤에 가장 중요한 오토레이아웃을 담은 setupUI라는 함수를 코드 스니펫으로 만들어 쓰고 있는데, 각 코드에 들어갈 프로퍼티들이 다르다보니 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&amp;nbsp;&amp;lt;#프로퍼티#&amp;gt; &lt;/span&gt;&amp;nbsp;와 같이 입력값을 받을 수 있게 설정해두었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.25.56.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;568&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CM4ij/btssJkiggaw/5WDLRoTuxmR5anKJEi8Np0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CM4ij/btssJkiggaw/5WDLRoTuxmR5anKJEi8Np0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CM4ij/btssJkiggaw/5WDLRoTuxmR5anKJEi8Np0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCM4ij%2FbtssJkiggaw%2F5WDLRoTuxmR5anKJEi8Np0%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;574&quot; height=&quot;484&quot; data-filename=&quot;스크린샷 2023-08-30 오후 9.25.56.png&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;568&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;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <category>코드스니펫</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/46</guid>
      <comments>https://ahrzosel.tistory.com/46#entry46comment</comments>
      <pubDate>Wed, 30 Aug 2023 21:29:08 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (5) : MVC 구조</title>
      <link>https://ahrzosel.tistory.com/45</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;MVC(Model-View-Controller)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnTQPQ/btssCsOkdVI/jnXWxzJfsxV5qiJMSl8nP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnTQPQ/btssCsOkdVI/jnXWxzJfsxV5qiJMSl8nP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnTQPQ/btssCsOkdVI/jnXWxzJfsxV5qiJMSl8nP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnTQPQ%2FbtssCsOkdVI%2FjnXWxzJfsxV5qiJMSl8nP1%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;2870&quot; height=&quot;1554&quot; data-origin-width=&quot;2870&quot; data-origin-height=&quot;1554&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC는 소프트웨어 디자인 패턴으로, 앱의 구성 요소들을 세가지 주요 컴포넌트로 분리하여 코드의 유지보수성과 재사용성을 높여준다.&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;Model(모델)&lt;/h4&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;li&gt;변경 사항이 있을 때 컨트롤러에게 알리는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693310128088&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Todo {
    var title: String
    var category: String
    var isCompleted: Bool
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;View(뷰)&lt;/h4&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;li&gt;컨트롤러에게 사용자의 입력과 이벤트를 알리는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693310208826&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TodoTableViewCell: UITableViewCell {
    // 테이블 뷰 셀을 커스텀하여 배치 - 사용자 인터페이스 및 시각적 표현
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var categoryLabel: UILabel!
    @IBOutlet weak var completionCheckmark: UIImageView!
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Controller(컨트롤러)&lt;/h4&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;li&gt;뷰와 모델의 결합도를 낮춰 유연성을 높임&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693310342536&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TableViewController: UITableViewController {
    var todoList: [Todo] = []  // 모델 데이터
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // todoList 초기화 등의 로직
    }
    
    // 테이블 뷰 데이터 소스 및 델리게이트 메서드 구현
    // ...
    
    // 할 일 추가 버튼을 눌렀을 때 호출되는 메서드
    @IBAction func addTodoButtonTapped(_ sender: UIButton) {
        // TodoManager를 사용하여 할 일 추가
        TodoManager.shared.addTodo(title: &quot;새 할 일&quot;, content: &quot;&quot;, category: &quot;일반&quot;, isCompleted: false)
        tableView.reloadData()
    }
}&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;MVC 구조는 상기와 같으나, 내가 개발한 코드는 View와 Controller가 거의 밀접하게 연관되어 대체적으로 ViewController에서 모든 로직을 수행하는 구조로 되어있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스토리보드로 앵간한 UI는 다 구현하다보니, View로 분류할 코드가 다 VC에 작성되어버렸다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 TodoManager 같은 경우에는 컨트롤러라고 볼 수 있지만, 데이터를 다루는 역할을 하기 때문에 모델로 분류했다.&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;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MVC 장단점&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분리된 역할: 각 컴포넌트인 모델, 뷰, 컨트롤러는 서로 다른 역할을 수행하기 때문에 코드의 가독성이 높으며, 이해하기 쉬움. 또한, 데이터 로직, UI, 비즈니스 로직이 분리되어 개발과 유지보수 용이&lt;/li&gt;
&lt;li&gt;유연상과 재사용성: 각 컴포넌트가 독립적으로 작동하므로 변경 사항을 다른 컴포넌트에게 영향을 주지 않고 쉽게 구현 및 교체 가능&lt;/li&gt;
&lt;li&gt;협업 용이: 역할 및 책임이 분명히 나누어져 있어 여러 개발자가 협업 시 코드의 충돌을 줄이고 독립적인 작업 가능&lt;/li&gt;
&lt;li&gt;테스트 용이: 개별 테스트 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&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;li&gt;복잡성 처리 어려움: 크고 복잡한 앱에서는 MVC 패턴만으로는 모든 구조를 처리하기 어려울 수 있어 다른 아키텍처 패턴이나 구조를 고려해야 함&lt;/li&gt;
&lt;/ul&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;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그외 소프트웨어 아키텍처 패턴&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVVM(Model-View-ViewModel)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVC 패턴을 확장한 형태로 뷰와 모델 사이에 뷰모델이 추가됨&lt;/li&gt;
&lt;li&gt;뷰모델은 데이터 바인딩을 통해 연결되며, 뷰와 모델 사이의 중개자 역할을 하며 UI 로직을 처리&lt;/li&gt;
&lt;li&gt;데이터 바인딩을 통해 뷰와 모델이 동기화되므로 코드의 양을 줄이고 유연한 UI 구성이 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVP(Model-View-Presenter)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVP는 컨트롤러 대신에 프리젠터가 사용되는 패턴&lt;/li&gt;
&lt;li&gt;프리젠터는 뷰와 모델 사이의 중개자 역할을 하며, 뷰에서 사용자의 입력을 받아 모델을 업데이트하고 뷰를 갱신&lt;/li&gt;
&lt;li&gt;뷰와 프리젠터는 인터페이스로 연결되어 UI와 로직이 분리됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;VIPER&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰, 인터프리터(Interactor), 프리젠터, 엔티티(Entity), 라우터(Router)로 구성된 아키텍처 패턴&lt;/li&gt;
&lt;li&gt;모듈화와 각 역할의 분리를 강조하여 복잡한 앱에서의 유지보수와 확장성을 항상시킴&lt;/li&gt;
&lt;li&gt;각 구성 요소는 역할에 따라 엄격하게 구분되어 코드의 의존성을 최소화함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Clean Architecture&lt;/h4&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;h4 data-ke-size=&quot;size20&quot;&gt;Flux, Redux&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flux와 Redux는 상태 관라 패턴으로, 어플리케이션의 상태를 중앙 저장소에서 관리하는 방식&lt;/li&gt;
&lt;li&gt;상태 변경을 예측 가능한 방식으로 처리하여 상태 관리의 복잡성을 줄임&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때까지 실습을 대체적으로 스토리보드로 UI를 구현하고 그에 따른 로직을 ViewController에서 작업하다보니 뷰와 컨트롤러의 경게가 모호해지는 일이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 복잡도나 어려움의 정도가 적어서 그대로 유지할 수 있었지만, 향후 프로젝트 규모가 커지면 스파게티 코드가 될 것 같아 벌써 아득해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC 구조 외에도 여러가지 소프트웨어 아키텍처 패턴이 있으므로, 각 패턴(구조)의 장단점을 알아보고 최적의 구조를 선택해서 개발을 하면 될 것 같다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/45</guid>
      <comments>https://ahrzosel.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 29 Aug 2023 21:16:21 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (4) : TableView 오류 해결하기</title>
      <link>https://ahrzosel.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰는 다루면 다룰수록 새로운 에러와 마주하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 할일 확인하기 목록에서의 indexPath와 완료한 일 보기에서의 indexPath 값이 달라서 발생하는 문제를 마주했고 또 같은 실수를 반복하지 않기 위해 &lt;span style=&quot;color: #333333; 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&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/beEFPL/btssvfWhrjs/rDTltaxZlRUDLzODb2UF1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beEFPL/btssvfWhrjs/rDTltaxZlRUDLzODb2UF1k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;Simulator Screenshot - iPhone 14 Pro - 2023-08-28 at 19.10.32.png&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/beEFPL/btssvfWhrjs/rDTltaxZlRUDLzODb2UF1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEFPL%2FbtssvfWhrjs%2FrDTltaxZlRUDLzODb2UF1k%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;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zJJkN/btssvvrcX2L/70WCbyqNgTXimfuPTrhnek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zJJkN/btssvvrcX2L/70WCbyqNgTXimfuPTrhnek/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot; data-filename=&quot;Simulator Screenshot - iPhone 14 Pro - 2023-08-28 at 19.10.42.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zJJkN/btssvvrcX2L/70WCbyqNgTXimfuPTrhnek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzJJkN%2FbtssvvrcX2L%2F70WCbyqNgTXimfuPTrhnek%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;1179&quot; height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/44</guid>
      <comments>https://ahrzosel.tistory.com/44#entry44comment</comments>
      <pubDate>Mon, 28 Aug 2023 19:39:09 +0900</pubDate>
    </item>
    <item>
      <title>7주차 WIL</title>
      <link>https://ahrzosel.tistory.com/43</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FACTS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;프로젝트명 : 메모앱 만들기 심화&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #666666; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FINDINGS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;table style=&quot;background-color: #ffffff; color: #5c5c5c; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #666666; text-align: center; width: 10.5814%;&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #666666; width: 89.3023%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;테이블뷰의 헤더가 보이지 않음(스토리보드 작업)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;카테고리 구분을 위해 Set으로 배열을 만들었는데 빌드하니 순서가 뒤죽박죽이 됨&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;셀을 선택하여 디테일페이지로 이동하는데 셀의 내용이 처음 추가한 셀의 내용과 동일하게 나옴&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;별도의&amp;nbsp; 카테고리 배열을 만들지않고 딕셔너리로 memoList의 데이터 구조 변경하였으나, 입력값을 인식 못해서 메모 추가가 안됨&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;updateMemo의 guard문이 false로 인지하고 있어 업데이트가 안됨&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;디테일페이지에서 수정 후 저장누르면 이전화면으로 돌아가지 않음(popViewController 안먹음)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot;&gt;랜덤 고양이 사진 불러올 때 URLSession이 해당 페이지에서 작업을 하지 않는데도 계속 진행 중, 콘솔에 알람뜨고 시간 지나니&lt;span style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot;&gt;까 타임아웃 에러 발생&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;color: #666666; text-align: center; width: 10.5814%;&quot;&gt;&lt;b&gt;해결방안&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;color: #666666; width: 89.3023%;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;테이블뷰의 스타일을 Grouped으로 설정해야 함(코드, 스토리보드 동일)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Set은 순서가 없으므로 sorted된 변수를 만들어서 사용&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;tableView[indexPath.row]를 카테고리에 맞는 값으로 변경&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(해결방안 찾지 못함,,,) 카테고리가 처음에 없어서 생성해줘야 하는 듯 -&amp;gt; 추후 딕셔너리로 데이터 구조 변경해서 해결해보려고 함&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;디버깅이나 프린트 문 넣어서 문제 파악이 먼저, guard문 내 조건을 선택된 카테고리 내 메모리스트의 수로 변경&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Alert 이후 dismiss 되어 popVC이 실행될 시간이 없어서 실행이 안됨, DispatchQueue를 통해 실행&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span&gt;viewWillDisappear에서 세션 종료 시점을 설정하거나, task를 실행할 때 종료하도록 설정(finishTasksAndInvalidate)&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FUTURE&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;구조체와 클래스를 사용하는 이유에 대해서 좀 더 고민할 필요가 있음&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;테이블뷰 기초부터 연습하기&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;문제 발생 시 디버깅이나 프린트문으로 원인 찾기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;FEELINGS&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #5c5c5c; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;테이블뷰에 대한 기초 지식이 부족한 것 같다.. 매번 똑같은 부분에서 문제가 발생하는데 자꾸 원인을 까먹음 ㅠㅠ&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;문제가 발생할 때 냅다 검색하는 나를 보면서 뭔가 스스로 해결하려는 부분이 부족하다고 느낀다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이론이 부족한 상태에서 실습을 하려니 자꾸 벽에 부딪히는 느낌이다.. '일단 손에 익어야하니까 해보자'랑 '모르는데 어떻게 해요'가 대립하는 중,,,, ㅋㅋㅋ큐ㅠㅠㅠㅠㅠ&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;뭐 그래도 어쩌겠습니까.. 해 내야죠!!!!&amp;nbsp;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;화이팅하자!! 포기하지 말자!! 내 자신한테 응원하자!!&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>WIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/43</guid>
      <comments>https://ahrzosel.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 28 Aug 2023 09:43:16 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (3) : URL Session 으로 API 통신하여 고양이 사진 가져오기</title>
      <link>https://ahrzosel.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;URL로 되어 있는 외부 API를 사용해서 메모 앱에 추가하려고 한다.&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;The Cat API(&lt;a href=&quot;https://thecatapi.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://thecatapi.com&lt;/a&gt;)에서 제공하는 API를 활용하였고, 자세한 가이드는 &lt;a href=&quot;https://developers.thecatapi.com/view-account/ylX4blBYT9FaoVd6OhvR?report=bOoHBz-8t&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 홈페이지&lt;/a&gt;에서 참조하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작에 앞서, 해당 API를 호출하면 다음과 같은 결과를 받을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692972174344&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[
  {
    &quot;id&quot;: &quot;e4f&quot;,
    &quot;url&quot;: &quot;https://cdn2.thecatapi.com/images/e4f.jpg&quot;,
    &quot;width&quot;: 500,
    &quot;height&quot;: 375
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 결과값은 JSON 형태로 제공되므로, 데이터를 받아올 때 변환해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료로 제공하는 갯수는 10개이며, 그 이상의 데이터 수를 얻기 위해서는 가입해서 API KEY를 받아 설정하면 된다.&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;그리고 고려해야 할 점은 ViewController의 Life Cycle을 고려하여 호출되는 시점을 정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URLSession를 통해 API 데이터 가져오기&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;URL 생성&lt;/li&gt;
&lt;li&gt;URLSession 인스턴스 생성&lt;/li&gt;
&lt;li&gt;Data Task 생성&lt;/li&gt;
&lt;li&gt;Data Task 실행&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 data-ke-size=&quot;size20&quot;&gt;간단한 예제코드&lt;/h4&gt;
&lt;pre id=&quot;code_1692973062118&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. URL 생성
let apiUrl = URL(string: &quot;https://api.example.com/data&quot;)

// 2. URLSession 인스턴스 생성
let session = URLSession.shared

// 3. Data Task 생성
let dataTask = session.dataTask(with: apiUrl!) { (data, response, error) in
    if let error = error {
        print(&quot;Error: \(error)&quot;)
        return
    }
    
    // 4. 데이터 처리
    if let data = data {
        // 데이터 파싱 및 활용
        print(&quot;Received data: \(data)&quot;)
    }
}

// 5. Data Task 실행
dataTask.resume()&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. URL 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 가져올 데이터를 제공하는 API의 URL을 생성한다. 이 때 필요한 파라미터나 경로 등을 포함할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692973019431&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let url = URL(string: &quot;https://api.thecatapi.com/v1/images/search&quot;)!
// 만약 api key 나 품종 등 추가 설정이 존재할 경우 커스터마이징

guard let url = URL(String: &quot;https://api.thecatapi.com/v1/images/search&quot;) else { return }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 해당 url이 nil이 될 가능성이 없다고 판단하여 강제 옵셔널 해제를 하였지만, 혹시 모를 nil에 대응하기 위해 옵셔널 바인딩 하는 것을 추천&lt;/li&gt;
&lt;li&gt;guard let 구문을 활용하여 nil 일 경우 에러 메세지를 보여줄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. URLSession 인스턴스 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession 클래스의 인스턴스를 생성하여 네트워크 작업을 수행할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692973249926&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let session = URLSession(configuration: .default)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;configuration 특징 : 캐시 및 쿠키 사용, 인증과 관련된 기본 설정, Connection Pooling*, 백그라운드 업로드 및 다운로드, 캐시 정책 설정, 타임아웃 설정&lt;br /&gt;* 여러 네트워크 작업 간에 연결을 재사용하여 성능을 최적화 함&lt;/li&gt;
&lt;li&gt;default : 기본적인 설정을 사용하는 configuration으로 &amp;nbsp;일반적인 네트워크 작업에 적합함&lt;/li&gt;
&lt;li&gt;ephemeral : 일시적인 데이터 요청에 적합. 캐시 및 쿠키를 저장하지 않고 임시적인 세션을 사용하여 데이터 요청을 처리&lt;/li&gt;
&lt;li&gt;background : 백그라운드에서 데이터 업로드 및 다운로드를 처리. 앱이 백그라운드로 들어갔을 때 네트워크 작업을 계속할 수 있음&lt;/li&gt;
&lt;li&gt;custom : 사용자 정의 설정을 통해 네트워크 작업에 특정한 옵션을 정확하게 조정할 수 있음&lt;/li&gt;
&lt;/ul&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;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Task를 생성하기 전에 해당 url의 데이터를 받을 객체를 생성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 url은 id, url, width, height 로 구성되어 있으며 나는 url만 필요해서 아래와 같이 간단하게 모델을 생성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1692973887486&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct RandomImage: Codable {
    let url: String
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Data Task 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URLSession 인스턴스를 사용하여 데이터를 가져오기 위한 Data Task를 생성한다. 생성된 Task는 백그라운드에서 실행되며 네트워크 요청을 처리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692974012507&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session.dataTask(with: url) { data, response, error in
    guard let data = data, error == nil else {
    	return 
    }
    
    // Data Task 실행 코드
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;session.dataTask(with:completionHandler:) : URLSession에서 데이터를 가져오는 작업을 생성. with - 가져올 데이터의 URL, Handler - 네트워크 작업이 완료되었을 때 실행할 클로저를 전달&lt;/li&gt;
&lt;li&gt;data : 서버로부터 받아온 데이터&lt;/li&gt;
&lt;li&gt;response : 서버의 응답에 대한 정보가 담겨있는 객체&lt;/li&gt;
&lt;li&gt;error : 네트워크 작업 중 발생한 에러. nil인 경우 네트워크 작업이 성공적으로 완료된 것을 의미&lt;/li&gt;
&lt;li&gt;상기 코드는 data가 nil이 아니고 error가 nil인 경우에만 실행되는 코드로 데이터가 성공적으로 받아와짐을 의미&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Data Task 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 Data Task를 실행하여 데이터를 가져온다. 이 때 다양한 옵션을 설정하여 요청을 커스터마이즈 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692974484487&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session.dataTask(with: url) { data, response, error in
    guard let data = data, error == nil else {
    	return 
    }
    
   ...
    
}.resume()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성된 Data Task를 불러와 마지막에 .resume()을 붙여 실행할 수도 있고, 상기 코드처럼 바로 실행할 수도 있음&lt;/li&gt;
&lt;li&gt;resume()을 호출함으로써 데이터 작업이 서버로 요청을 보내고, 서버 응답을 기다리며 데이터를 받아오는 등의 동작이 시작됨&lt;/li&gt;
&lt;li&gt;resume()을 호출하면 앞서 정의한 클로저가 실행되며, 클로저 내부에서 데이터를 받아와서 처리&lt;/li&gt;
&lt;li&gt;비동기적으로 네트워크 작업을 수행하면 앱이 멈추지 않고 다른 작업을 수행할 수 있게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 데이터 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Task가 완료되면 서버로부터 받은 데이터 또는 에러 정보를 처리한다. 성공적으로 데이터를 가져온 경우 원하는 형식으로 파싱하고 활용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692974743847&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let decoder = JSONDecoder()     // (1)
guard let randomCatImage = try? decoder.decode([RandomImage].self, from: data) else { return } // (2)
if let imageUrl = URL(string: randomCatImage.first?.url ?? &quot;&quot;) {     // (3)
    URLSession.shared.dataTask(with: imageUrl) { imageData, _, _ in     // (4)
        if let imageData = imageData, let image = UIImage(data: imageData) {     // (5)
            DispatchQueue.main.async {     // (6)
                self.randomImageView.image = image
            }
        }
    }.resume()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상기 코드는 JSON 데이터를 파싱하여 이미지 url을 가져오고, 해당 url을 통해 이미지 데이터를 받아와 imageView에 나타냄&lt;/li&gt;
&lt;li&gt;(1) : JSON 데이터를 디코딩하기 위한 JSONDecoder 인스턴스 생성&lt;/li&gt;
&lt;li&gt;(2) : 서버에서 받아온 JSON 데이터(data)를 앞서 정의한 RandomImage 타입으로 디코딩하고 randomCatImage 변수에 저장. 만약 실패하면 함수 실행 종료&lt;/li&gt;
&lt;li&gt;(3) : randomCatImage에 저장된 첫 번째 이미지 URL을 가져옴. nil이 아닐 경우에만 실행&lt;/li&gt;
&lt;li&gt;(4) : (3)에서 가져온 이미지 url을 이용하여 새로운 dataTask를 생성&lt;/li&gt;
&lt;li&gt;(5) : imageData가 nil이 아닐경우 UIImage로 변환&lt;/li&gt;
&lt;li&gt;(6) : 이미지뷰 업데이트는 메인 스레드에서 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. 에러 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data Task 실행 중에 에러가 발생할 경우를 대비라여 적정한 에러 처리를 구현한다. 네트워크 연결 오류, 서버 응답 오류 등을 처리할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1692975592016&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;URLSession.shared.dataTask(with: imageUrl) { data, response, error in
    if let error = error {
        print(&quot;Error: \(error)&quot;)
    }
    if let httpResponse = response as? HTTPURLResponse {
        print(&quot;Status Code: \(httpResponse.statusCode)&quot;)
    }
    if let imageData = data, let image = UIImage(data: imageData) {
        DispatchQueue.main.async {
            self.randomImageView.image = image
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;error : 에러 정보 프린트&lt;/li&gt;
&lt;li&gt;response : HTTP 응답에 대한 정보 프린트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. 세션 종료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 모든 데이터를 가져온 뒤에는 URLSession을 종료하여 리소스 관리와 메모리 누수를 방지한다.&lt;/p&gt;
&lt;pre id=&quot;code_1692976091027&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session.finishTasksAndInvalidate()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;invalidateAndCancel : 세션을 무효화하고 진행 중인 작업을 취소. 현재 세션과 해당 세션 내 모든 작업 중단&lt;/li&gt;
&lt;li&gt;finishTasksAndInvalidate : 현재 진행 중인 작업을 완료한 후 세션을 무효화. 현재 작업들이 완료될 때까지 기다린 후 세션 종료&lt;/li&gt;
&lt;li&gt;아무 코드도 작성하지 않으면 앱 수명동안 유지되며, 작업이나 Task를 계속해서 추가할 수 있음&lt;/li&gt;
&lt;/ul&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;pre id=&quot;code_1692976283477&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import SkeletonView

class PetViewController: UIViewController {
    var imageList: [RandomImage] = []
    
    @IBOutlet weak var randomImageView: UIImageView!
    @IBOutlet weak var randomButton: UIButton!
    @IBOutlet weak var catImageView: UIImageView!
    @IBAction func randomButton(_ sender: Any) {
        getRandomCatImage()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        randomImageView.showAnimatedGradientSkeleton()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
            self?.randomImageView.hideSkeleton()
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        getRandomCatImage()
    }
    
    func getRandomCatImage() {
        guard let url = URL(string: &quot;https://api.thecatapi.com/v1/images/search&quot;) else { return }
        let session = URLSession(configuration: .ephemeral)
        let task = session.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else { return }
            let decoder = JSONDecoder()
            guard let randomCatImage = try? decoder.decode([RandomImage].self, from: data) else { return }
            if let imageUrl = URL(string: randomCatImage.first?.url ?? &quot;&quot;) {
                let imageTask = URLSession.shared.dataTask(with: imageUrl) { data, response, error in
                    if let error = error {
                        print(&quot;Error: \(error)&quot;)
                    }
                    if let httpResponse = response as? HTTPURLResponse {
                        print(&quot;Status Code: \(httpResponse.statusCode)&quot;)
                    }
                    if let imageData = data, let image = UIImage(data: imageData) {
                        DispatchQueue.main.async {
                            self.randomImageView.image = image
                            session.finishTasksAndInvalidate()
                            print(&quot;세션 종료&quot;)
                        }
                    }
                }
                imageTask.resume()
            }
        }
        task.resume()
    }
}&lt;/code&gt;&lt;/pre&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/cAXx7S/btssfx4MP7E/G0o41TKgzD3ZEpFHzkogVK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAXx7S/btssfx4MP7E/G0o41TKgzD3ZEpFHzkogVK/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot; data-filename=&quot;Simulator Screen Recording - iPhone 14 Pro - 2023-08-26 at 00.26.34.gif&quot; width=&quot;236&quot; height=&quot;512&quot; data-widthpercent=&quot;22.14&quot; style=&quot;width: 21.88478%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAXx7S/btssfx4MP7E/G0o41TKgzD3ZEpFHzkogVK/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAXx7S%2Fbtssfx4MP7E%2FG0o41TKgzD3ZEpFHzkogVK%2Fimg.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;295&quot; height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCiMEt/btssc6M8csP/13j50ZFp4GQngbc1pYOHTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCiMEt/btssc6M8csP/13j50ZFp4GQngbc1pYOHTk/img.png&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;414&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;77.86&quot; style=&quot;width: 76.952429%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCiMEt/btssc6M8csP/13j50ZFp4GQngbc1pYOHTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCiMEt%2Fbtssc6M8csP%2F13j50ZFp4GQngbc1pYOHTk%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;671&quot; height=&quot;414&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하고보니 문제아닌 문제로 느끼는건 바로 로딩 속도... 너무 느리다... 한국패치 plz  &lt;/p&gt;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>URLSession</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/42</guid>
      <comments>https://ahrzosel.tistory.com/42#entry42comment</comments>
      <pubDate>Sat, 26 Aug 2023 00:30:51 +0900</pubDate>
    </item>
    <item>
      <title>[Swift] 메모 앱 만들기 심화 (2) : TableView Section/Header/Footer</title>
      <link>https://ahrzosel.tistory.com/41</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;TableView Section/Header/Footer&lt;br /&gt;&lt;/b&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;pre id=&quot;code_1692876370815&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    // 더미 데이터
    let data = [
        [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;],
        [&quot;one&quot;, &quot;two&quot;, &quot;three&quot;],
        [&quot;1&quot;, &quot;2&quot;, &quot;3&quot;]
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let tableView = UITableView(frame: view.bounds, style: .grouped)
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
        
        // 테이블뷰에 헤더와 푸터 등록
        tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: &quot;header&quot;)
        tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: &quot;footer&quot;)
    }
    
    // 섹션 수 반환
    func numberOfSections(in tableView: UITableView) -&amp;gt; Int {
        return data.count
    }
    
    // 각 섹션의 셀 수 반환
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return data[section].count
    }
    
    // 셀 생성 및 데이터 삽입
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &quot;cell&quot;, for: indexPath)
        cell.textLabel?.text = data[indexPath.section][indexPath.row]
        return cell
    }
    
    // 섹션 헤더 뷰 설정
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -&amp;gt; UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: &quot;header&quot;)!
        headerView.textLabel?.text = &quot;Section \(섹션 + 1) Header&quot;
        return headerView
    }
    
    // 섹션 푸터 뷰 설정
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -&amp;gt; UIView? {
        let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: &quot;footer&quot;)!
        footerView.textLabel?.text = &quot;Section \(섹션 + 1) Footer&quot;
        return footerView
    }
}&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;h4 data-ke-size=&quot;size20&quot;&gt;문제1. 테이블뷰의 섹션 및 헤더 Height 값을 지정했으나 시뮬레이터에 나오지 않음&lt;/h4&gt;
&lt;pre id=&quot;code_1692876632895&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -&amp;gt; CGFloat {
	return 10.0
}

override func numberOfSections(in tableView: UITableView) -&amp;gt; Int {
	return 5 
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(원인) 테이블뷰의 스타일이 Plain로 설정되어 있음&lt;/li&gt;
&lt;li&gt;(해결방안) 테이블뷰 스타일을 Grouped로 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블 뷰의 레이아웃과 섹션 헤더를 표시하는 방식을 변경하기 위해서는 스타일을 Grouped로 설정해야 함&lt;/li&gt;
&lt;li&gt;섹션 헤더와 푸터가 각 섹션 위와 아래에 표시되며, 섹션과 셀 사이에 여백이 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;문제2. 섹션으로 나누고 난 후 메모 스위치 작동 시 업데이트 안됨&lt;/h4&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;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;style6&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;생각보다 섹션을 나누고 헤더와 푸터를 넣는 일은 어렵지 않았다.&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&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/b7vu3x/btsrZxYYWhx/kaOSSU7o5GqQTMofehleL1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7vu3x/btsrZxYYWhx/kaOSSU7o5GqQTMofehleL1/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;3783&quot; data-filename=&quot;IMG_2073.jpeg&quot; width=&quot;516&quot; height=&quot;1333&quot; data-widthpercent=&quot;34.79&quot; style=&quot;width: 34.382604%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7vu3x/btsrZxYYWhx/kaOSSU7o5GqQTMofehleL1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7vu3x%2FbtsrZxYYWhx%2FkaOSSU7o5GqQTMofehleL1%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;1464&quot; height=&quot;3783&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sOsee/btssaaBaUnU/Gp9Y0fFmj6IwAKqkAhCgKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sOsee/btssaaBaUnU/Gp9Y0fFmj6IwAKqkAhCgKk/img.jpg&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;2018&quot; data-filename=&quot;IMG_2073.jpeg&quot; data-widthpercent=&quot;65.21&quot; style=&quot;width: 64.454605%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sOsee/btssaaBaUnU/Gp9Y0fFmj6IwAKqkAhCgKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsOsee%2FbtssaaBaUnU%2FGp9Y0fFmj6IwAKqkAhCgKk%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;1464&quot; height=&quot;2018&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; color: #000000; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 메모에는 카테고리가 있고, 그 카테고리는 디테일페이지에서 팝업버튼으로 구현해 놓은 상태&lt;/li&gt;
&lt;li&gt;섹션의 갯수는 TableViewController에 category라는 배열을 만들어 5가지를 미리 지정해 놓음&lt;br /&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;category.count로 섹션 갯수 return&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;만약에 카테고리를 추가로 커스텀할 수 있다고 가정하면, 해당 배열에 추가되야 하고 메뉴버튼에도 추가되어야 함(메뉴버튼 추가는 어떻게 하는지 찾아봐야 함 ㅠ)&lt;/li&gt;
&lt;li&gt;셀의 수는 for문으로 category 배열을 돌면서 if문으로 memoList.category와 동일한지 확인&lt;/li&gt;
&lt;li&gt;동일하다면 해당 카테고리만 포함하는 메모의 배열에다 추가(굳이..? 그럴거면 메모매니저에서 하는게 낫지&amp;hellip; 근데 동일한 데이터를 여러개 만들 필요가 있을까?)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692877253665&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 처음 고민
let category = [&quot;일반&quot;, &quot;반려동물&quot;, &quot;집&quot;, &quot;과제&quot;, &quot;운동&quot;]`
    
override func numberOfSections(in tableView: UITableView) -&amp;gt; Int {
	return category.count 
}
    
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
	// 카테고리 배열의 값과 memoList의 카테고리 값이 매칭되면 해당 memoList의 카운트를 반환
	for index in 0..&amp;lt;category.count {
		if category[index] == myMemo.memoList[index].category {
        	// 실행할 코드
        }
    return myMemo.memoList[section].count 
    }
}&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;일단 기존 데이터 구조를 살려 배열로 memoList를 유지하는 방법으로 진행했다.&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;문제3. memoList에 있는 카테고리를 Set을 통해 집합을 만들었으나 실행하니 순서가 뒤죽박죽이 됨&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(원인) set 함수는 순서가 없음! 따라서 sorted된 변수(상수)를 만들어서 사용해야 함&lt;/li&gt;
&lt;li&gt;(해결방안)&lt;span&gt; &lt;/span&gt;let categories = Array(Set(MemoManager.shared.memoList.map { $0.category ?? &quot;일반&quot; })).sorted()&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 전역변수로 선언하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제4. 디테일페이지로 들어가면 섹션의 셀의 내용이 처음 추가한 셀의 내용과 동일하게 나옴&lt;/h4&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;span&gt;&amp;nbsp;&lt;/span&gt;tableView[indexPath.row]를 카테고리에 맞는 값으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692877540933&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@IBAction func memoSwitch(_ sender: UISwitch) {
    guard let tableView = superview as? UITableView,
    	let indexPath = tableView.indexPath(for: self) else {
        	return 
        }
    let categories = Array(Set(MemoManager.shared.memoList.map { $0.category ?? &quot;일반&quot; })).sorted()
    let category = categories[indexPath.section]
    let memoListInSection = MemoManager.shared.memoList.filter { $0.category == category }
    var memo = memoListInSection[indexPath.row]
    memo.isCompleted = sender.isOn
    updateLabelStrikeThrough()
    MemoManager.shared.updateMemo(at: indexPath.row, newContent: memo.content, isCompleted: memo.isCompleted, insertDate: memo.insertDate, targetDate: memo.targetDate, priority: memo.priority, category: memo.category, progress: memo.progress)
    // 로그 출력 (Memo 객체의 내용 출력)
    for memo in MemoManager.shared.memoList { print(memo) }
}&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;메모를 추가하고 수정하고 로드할 때마다 계속 필터로 카테고리를 찾아야 하니 메모리가 많이 사용되지 않을까 하며..&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;고민. filter 함수쓰면 매번 필터링을 돌아야해서 메모리를 많이 쓰지 않을까?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 구조 변경 :&amp;nbsp;memoList = [Memo] 배열 &amp;rarr; memoList = [String : [Memo]] 딕셔너리&lt;/li&gt;
&lt;li&gt;String은 key 값이고 해당 key를 카테고리로 변경&lt;/li&gt;
&lt;li&gt;&lt;s&gt;&lt;i&gt;와 그냥 필터쓰는게 나앗을 듯&amp;hellip;&amp;hellip;&amp;hellip;.&lt;/i&gt;&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제5. 딕셔너리로 데이터 구조를 변경하였으나, 입력값을 인식 못해 메모가 추가되지 않음&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(원인) 카테고리가 처음에 없어서 생성해줘야 함(아니 근데 애초에 메모 추가할 때 카테고리를 지정했는데 그 value에 있는 카테고리를 key값으로 받아오지를 못하나?)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제6. 카테고리는 만들어지는데 메모가 수정이 안됨&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0g1pM/btsr0WEfFWy/UiLXU7KibRWIhXM8i3x7EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0g1pM/btsr0WEfFWy/UiLXU7KibRWIhXM8i3x7EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0g1pM/btsr0WEfFWy/UiLXU7KibRWIhXM8i3x7EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0g1pM%2Fbtsr0WEfFWy%2FUiLXU7KibRWIhXM8i3x7EK%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;552&quot; height=&quot;314&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;314&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;(원인) 뭔지도 모르겠음,...&amp;nbsp;&lt;/li&gt;
&lt;li&gt;(원인파악) 디버깅 시도 하였으나, 브레이크 포인트가 안먹음...(어이X), 함수 사이에 프린트문을 넣어서 확인해봤는데 조건을 만족하지 못해 업데이트 코드를 실행하지 못하고 return 됨&lt;/li&gt;
&lt;li&gt;뭔가.. 기존 데이터에 없던 카테고리를 선택하게 되면서 해당 카테고리를 만들어줘야 하는 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1692878336914&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if newMemoList[category] == nil {
    newMemoList[category] = []  // 해당 카테고리의 배열이 없다면 생성
}&lt;/code&gt;&lt;/pre&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;조건문 수정해서 메모 수정하는 코드로 진입했는데 에러... out of range래...&lt;/li&gt;
&lt;li&gt;당연하지... 메모가 없는데 접근을 하려고 하니까!!!!!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 몇시간 부여잡고 딕셔너리 포기하고 filter로 돌아옴...&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;</description>
      <category>TIL</category>
      <category>ios</category>
      <category>Swift</category>
      <category>Xcode</category>
      <category>앱개발</category>
      <author>너이르나</author>
      <guid isPermaLink="true">https://ahrzosel.tistory.com/41</guid>
      <comments>https://ahrzosel.tistory.com/41#entry41comment</comments>
      <pubDate>Thu, 24 Aug 2023 21:01:41 +0900</pubDate>
    </item>
  </channel>
</rss>