<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>BlueWIPER</title>
    <link>https://bluewiper.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 16 Jul 2026 16:23:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>bluewiper</managingEditor>
    <item>
      <title>[내일배움캠프 후기] iOS 개발 공부부터 앱출시까지 성공 + 사전캠프 본캠프 꿀팁</title>
      <link>https://bluewiper.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 5월에 처음 iOS 캠프에 들어오고 iOS 창업 캠프로 전환을 거쳐서 앱 출시까지 마쳤다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;결론부터 말하자면 개발 지식 0이었는데 OCR과 AI 기술을 접목한 일정 관리 앱을 만들었다.&lt;/b&gt; 혼자서는 아마 할 수 있을까? 하는 생각도 못했을 것이다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;공무원으로 오랫동안 근무하면서 &lt;b&gt;나는 개발의 ㄱ도 몰랐고&lt;/b&gt;, 사전 캠프를 거쳤지만 캠프 초중반에 이 실력으로 &lt;b&gt;앱출시가 가능할까하는 걱정&lt;/b&gt;이 많았다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;나만의 서비스를 만들고 싶다는 생각을 했다. UX 디자인이라는 개념이 슬슬 유명해질 때였어서 배우다가 디자이너로 전향하고 싶어졌다. 비전공자였기 때문에 개발 지식을 조금이라도 익혀놓으면 플러스가 될 것 같아서 개발 캠프를 찾게 되었다. 초기에 iOS 창업팀이 생겨서 마지막 정착지로 삼았다.&lt;/span&gt; &lt;b&gt;출시까지 하게 되는 과정이어서 나는 기획부터 출시까지 어떤 역할을 제일 잘하고 좋아할지에 대해 궁금하기도 했고, 나만의 서비스를 만드는 사람이 되고 싶은 마음에 참여하게 되었다.&amp;nbsp;&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;이 캠프는 마지막으로 갈수록 빛을 발한다. &lt;/b&gt;사실 나는 창업 아이디어가 없었다. 여기 와서 생기겠거니 하는 마음으로 들어왔다. 막상 들어오니 사람들이 저마다의 아이디어를 갖고 들어와서 내가 잘 맞출 수 있을까하는 걱정이 됐다. 말해줄 수 있는 것은 커리큘럼을 잘 따르면 할 수 있다. 초반에는 개발, 창업에 대한 인풋이 필요해서 정규 과정을 따라가기가 많이 벅찼었다. 그리고 내가 실력이 부족하다보니 자율성이 떨어지는 것 같아서 포기하고 싶었던 적도 여러번 있었다. 근데 그 고통의 시간을 지나고 해보고 싶은게 생겼을 때 비로소 튜터님들과 수강생들에게 물어가면서 자율적이고 능동적으로 프로젝트를 수행할 수 있게 되었다. &lt;b&gt;그리고 하고자 하는 게 있다면 그에 따른 지원이 정말 끝도 없이 들어온다.&amp;nbsp;&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이런 사람은 꼭 해봐라.&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;혼자 하면 흐지부지되는 사람. 자신을&amp;nbsp; 더 깊이 알아가고 싶은 사람.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가장 좋았던 것들을 뽑자면,&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 이게 무료라니..현직 튜터의 멘토링 : &lt;/b&gt;현직에서 개발하고 계신 분들이랑 창업을 하고 활발히 운영중이신 튜터님이 1on1으로 붙어서 같이 고민하고 길을 찾아서 좋다. 이게 무료라니..&lt;br&gt;* &lt;b&gt;실력을 고속성장시켜줄 수 있는 프로젝트와 그걸 지원해주는 멘토링, 각종 특강&lt;/b&gt; : 초중반에는 모두 동일한 과제를 위한 강의와 프로젝트를 진행한다. 갑자기 수준이 높아져서 좀 어렵긴한데 그만큼 아 이런 것도 있구나 코드를 쳐보면서 알게되는 것들이 있다. 어렵다고 포기하면 진짜 프로젝트 할 때 더 힘드니까 그냥 몰라도 따라친다는 생각으로 일단 하면 어느새 쪼오금 성장은 함.&amp;nbsp;&lt;br&gt;&lt;b&gt;* 좋은 문화 체득할 수 있는 코어와 수강생들과의 유대&lt;/b&gt; : 좋은 문화를 혼자서 체득하기란 어렵다. 개발자, 창업가, 혹은 같이 일하고 싶은 사람이 될 수 있도록 좋은 문화를 권장하고, 수강생과 튜터님과 생활하면서 이를 체득해나갈 수 있다.&lt;br&gt;&lt;b&gt;* 목표가 같은 사람들과 모여있음&lt;/b&gt; : 인생의 큰 변화를 주고 싶다면 목표가 같은 사람으로 내 주위를 채우라는 말이 있다. 그걸 해보고 싶어서 들어왔는데 확실히 건강한 사고를 할 수 있고 건설적으로 생각해볼 수 있게 된다.&amp;nbsp;&lt;br&gt;&lt;b&gt;* 혼자가 아니어요,,매니저님께 고민 상담&lt;/b&gt; : 나는 혼자 따라가기가 너무 너무 힘들 때면 매니저님께 솔직하게 상황을 털어놓고 어떻게 돌파하면 좋을지에 대해 상담해서 끝까지 올 수 있었던 것 같다. 처음이 어렵지 우리를 판단하러 계신 분들이 아니고 도와주려는 분들이니 도움이 필요하면 일단 가보길 추천. 일단 성품들이 장난 아니심. 수강 경험이 있는 분들이 주로 매니저님을 맡아주시는 것 같다. * 나는 이것까지 물어봤다 : 수강생들과의 대화가 좋지만 실력 차이를 메우고 싶은데 공부할 여유가 사라지는 것 같아서 너무 걱정이에요.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가장 기억에 남는 것은,&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오프라인 스터디 모임에서 팀원과 문제 해결하고 기쁨의 하이파이브했던 순간이다. 온라인으로만 봤던 수강생분들과 오프라인 스터디 모임을 한 적이 있다. 그때 팀프로젝트를 함께하던 팀원도 오셨어서 문제도 같이 고민하고, 특히나 짜릿했던 것은 문제를 해결했을 때 기쁨이다. 이건 경험해보면 못 헤어ㄴㅏ옵니다! 스포츠 경기를 같이 하는 것만큼 성취감이 높습니다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 기본적으로 매 과제마다 개인적으로 어려운 구간이 하나씩 있는데 차근 차근 깨나갈때마다 수강생과, 튜터님들과, 매니저님이 함께 기뻐하기 때문에 다 소중해~~~&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd08vy/btsJVKl5mzu/jLRkXbg2DvcM4xkN7wbKLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd08vy/btsJVKl5mzu/jLRkXbg2DvcM4xkN7wbKLK/img.png&quot; data-alt=&quot;최종 발표&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd08vy/btsJVKl5mzu/jLRkXbg2DvcM4xkN7wbKLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd08vy%2FbtsJVKl5mzu%2FjLRkXbg2DvcM4xkN7wbKLK%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;654&quot; height=&quot;476&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 발표&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;크게 달라진 것은,&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어만 있으면 만들고자 하는 것은 만들 수 있겠다는 자신감이 생긴다. 자신감만 생기는게 아니고 일단 시도할 수 있는 툴도 배우고 출시까지 해보기 때문에 경험에 의해 체감하는 것이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;개발 외적으로 좋았던 것은,&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;내가 위기상황에 어떻게 행동하는지 나를 직접 돌아보게 되고, 고쳐야하는 점이 뭐고, 실제로 고쳐서 다시 시도해보기도 하는 경험, 도움을 청할 때는 어떻게 해야하는지 배우는 것도 참 좋았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;공부만 하지 않는다. 사람과 어울리는 법 또한 배운다. 않이 개발 OX 퀴즈가 재미있을지 누가 알았겠냐며,,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;노션,,의 매력으로. 개발일지나 일정 관리 등 노션이랑 친해진다.&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2634&quot; data-origin-height=&quot;1408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKoPtE/btsJVpiboGm/Nd6ZQCVsnfs6FX3OkeSaD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKoPtE/btsJVpiboGm/Nd6ZQCVsnfs6FX3OkeSaD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKoPtE/btsJVpiboGm/Nd6ZQCVsnfs6FX3OkeSaD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKoPtE%2FbtsJVpiboGm%2FNd6ZQCVsnfs6FX3OkeSaD0%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;2634&quot; height=&quot;1408&quot; data-origin-width=&quot;2634&quot; data-origin-height=&quot;1408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리하며,&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다시 시간을 돌려서 5개월의 휴식시간이 주어진다고 해도 나는 이걸 했을 것이다.&amp;nbsp;&lt;br&gt;출석 잘해서 훈련장려금 꼭 받아가면서 하시길,, 화이팅입니당~~&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;[자신을 공부하게 만드는 팁]&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;스크럼과 피드백 시간을 주도해서 만들어라. 스크럼 하자고 해놓고, 공부한 거 정확히 설명하지 못하면 창피하쥬? 공부할 수밖에 없음.&amp;nbsp;&lt;br&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;0. &lt;b&gt;자습시간&lt;/b&gt;이지 노는 시간 아니에요 :&amp;nbsp;노는 시간 아님 공부하는 시간임. 인터스텔라처럼 나중에 후회하지 말고, 수강생들이랑 서로 공부 더 잘할 수 있도록 오늘 공부한 것들 공유하는 시간 꼭 가져보면 좋음. 창피해서라도 공부하게 됨&lt;br&gt;1. &lt;b&gt;무조건 문법 공부&lt;/b&gt; : 못알아들어도 어쩔 수 없다 무조건 많이 집어 넣어라. 수강 당시 개발 고수분이 추천해주신 방법인데 오 ㅐ 그때 더 열심히 하지 않았니...&lt;br&gt;2. &lt;b&gt;공식문서와 친해지기&lt;/b&gt; : 지금도 이게 뭔소리일까 싶은 글들이 많다. 그렇지만 블로그에는 개인의 해석이 들어가서 내게 맞는 정보가 아닐 수 있다. 사전캠프 때 많이 익숙해지는 게 좋다. 그냥 이런글이구나...하면서 이해하려고 하지 말고 그냥 아...이런 걸 써놓는구나만이라도 익숙해져 놓는 게 좋다.&amp;nbsp;&lt;br&gt;3. &lt;b&gt;블로그 예제&lt;/b&gt;를 보면서 만들어봐라&amp;nbsp;&lt;br&gt;4. 간단히 만들고 싶은 것들은 &lt;b&gt;Chat GPT로 예제&lt;/b&gt;를 찾아서 만들어보면서 문법과 친해지기&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;[캠프 생활]&amp;nbsp;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;1. 초반에는 성실히 나오면서&lt;b&gt; 9 to 9에 익숙해지기&lt;/b&gt; : 아침 9시부터 밤 9시까지 한 곳에서 시간을 보내는 것은 누구에게나 낯선 일이다. 실력이 모자랄수록 시간이 빠르게 흐르니, 초반에는 몰라도 일단 나온다는 생각으로 성실히 나오는게 중요하다.&lt;br&gt;2. 일단은 &lt;b&gt;하나 하나&lt;/b&gt;&amp;nbsp;&lt;b&gt;손으로 직접 코드&lt;/b&gt;를 짜보기 : 문법이 약할 수록 지급받은 강의를 이해하기 어렵다. 그럼 그냥 일단 손으로 쳐봐야 한다. 영어도 따라쓰면 익숙해지듯이 얘도 똑같은 것 같다.&amp;nbsp;&lt;br&gt;3. 뭘 해보고 싶었는지, 시도해본 것, 실패한 환경, 성공했던 방법 등과 같이 &lt;b&gt;매일 짧게라도 정리&lt;/b&gt;해놔라 : 나만의 답지 노트를 만들어서 나중에 또 써볼 코드에 대해 적어놓으면 복붙으로 시간 절약이 꽤 된다. 잘 쓰는 방법은 나도 모른다. 자기가 이해 잘 되기만 하면 된다. 그래도 여전히 검색해야하는 게 많긴하다. 그래도 반복적인 것들은 내가 제일 잘 알아들을 수 있는 언어로 정리하면 좋다. 나는 초반 이후에는 개인 노션에 적었는데 개발자로 취업하실 분들은&lt;b&gt; 블로그에 남겨놓으면 나중에 하나의 포트폴리오&lt;/b&gt;가 되어서 도움이 많이 된다고 한다. 짧게 생각만 해봐도 블로그로 남긴 사람과 안 남긴 사람 중 누가 더 믿음이 갈까?&amp;nbsp;&lt;br&gt;4. &lt;b&gt;공식문서부터&lt;/b&gt; 보려고 하기 : 애플 공식문서 뿐만 아니고 각종 라이브러리 판매사의 가이드 문서를 먼저 봐라. 이해 안 되는 건 블로그를 참조해보고, 그래도 이해 안 되는 건 튜터님께 여쭤보자.&amp;nbsp;&lt;br&gt;5. &lt;b&gt;튜터님과 친하게 지내자&lt;/b&gt; : 2시간동안 고민해도 어떻게 해야할지 감이 안 잡힐 때가 많았다. 아는 것도 양질의 인풋도 해놓은게 없으니까..ㅎㅎ 그럴 때는 일단 튜터님께 가서 상황을 설명하고 해보고 싶은 것을 의논하는 방식이 좋았다.&amp;nbsp;&lt;br&gt;6. &lt;b&gt;모두를 대할 때는 예의있게&lt;/b&gt;! : 다양한 사람들이 모이지만 특히 내배캠에서는 모두 좋은 사람들이었다. 성격적으로도 선하고 순한 분들이 많으니 항상 말을 예쁘게 하는 습관을 들인다면 서로 도움 주고 도움 받는 환경에서 정말 장점이 된다!&amp;nbsp;&lt;br&gt;7. &lt;b&gt;좋은 질문을 해야 좋은 답변을 받는다&lt;/b&gt; : 지금 뭘 해보고 싶었고, 어떤 검색을 마쳤는데 이게 활용이 어렵더라 아니면 시도했는데 안 되더라 어떤 것이 잘못되었는지 파악이 어렵다. 등을 정리하고 튜터님과 같이 시도해보고 싶은게 무엇인지에 명확히 노트에 적어서 가면 일단 서로의 시간을 아낄 수 있다. 나중에 답변만 따로 달아놓으면 되니까 좋은 자산이 된다. 좋은 질문하는 법도 특강으로 있 음. 만약 이번 커리큘럼에는 없다면 대체로 질문을 잘해서가 아닐까..&lt;br&gt;8. &lt;b&gt;피드백은  소중하다&lt;/b&gt;. :&amp;nbsp; 피드백은 공격이 아니다. 해주면 진짜로 오히려 감사한 것이다. 가끔 튜터님의 배경지식 내에서 가르쳐주실 때가 있어서 알아듣기 어려운 게 있는데 너무 따라가기 어려우면 배경지식이 어떤지 튜터님과 상의하면서 속도를 맞춰 갔으면 좋겠다. 그래도 못알아들었던 거는 다음에 갔을 때 알아들을 수 있도록 숙제는 스스로 찾아서 해야한다.&amp;nbsp;&lt;br&gt;9. &lt;b&gt;능동적으로 숙제 만들기&lt;/b&gt; : 못알아듣는 거 진짜 많다...실력이 0에서 빨리 느는 수강생분들 또는 이미 실력이 있으신 수강생분들이랑 친해져서 공부팁을 자기것으로 만들면 좋겠다. 공식 과제 외에도 부족한 점은 숙제로 만들어서 계속 채워나가야한다. 이미 실력차이가 있어서 안 하면 너무 수준이 벌어진다..ㅠㅠ&lt;br&gt;10. &lt;b&gt;Chat GPT가 능사&lt;/b&gt;가 아니다. : Chat GPT가 초기에는 상당부분 코드를 도와줄 수는 있는데 페이지가 많아질수록, 로직이 좀만 복잡해져도 정신 못차린다...문법을 꼭 공부해서 최소한 어디에서 얘가 잘못된 정보를 주고 있는지 정도는 알아채야 한다..나는 돈 주고 써봤는데도 페이지가 많아지니까 코드를 빼먹고 준다거나 할 때가 있었다. 쓰기 나름이니까 활용을 잘 하는 것도 중요!&amp;nbsp;&lt;br&gt;11. &lt;b&gt;문제를 바로 풀어주는 튜터님께 익숙해지지 말자&lt;/b&gt;. : 튜터님이 풀어준 건 내가 푼게 아니다. 이건 뭐..당시에는 좋을 수 있지만 장기적으로 봤을 때 튜터님과는 언젠간 헤어지고 혼자 해봐야하는 거기 때문에 당연하다.&amp;nbsp;&lt;br&gt;12. 팀과제 할 때 자기 파트가 끝나면 다른 사람들 파트도 연습삼아 해보길 : 나는 겁이 많아서 제 시간에 못하게 되면 팀원에게 피해를 줄까봐 항상 작은 파트를 받아서 겨우 해냈었다. 그런데 다른 팀원들의 얘기를 들어보니 자기파트가 아님에도&amp;nbsp;&lt;br&gt;13. &lt;b&gt;매니저님과 함께 자기 페이스를 찾아라&lt;/b&gt; : 실력 차이가 나면 따라가지 못한다고 너무 스트레스 받아서 그만두지 말고 더 열심히 해보자. 그럼 점차 실력이 그마나 나아져서 스트레스 덜 받게 됨. 그래야 비로소 해보고 싶은 것들을 제대로 시도해볼 수 있음. 그래도 너무 투머치다!! 그럼 자신을 너무 닦달하지 말고 조금 쉬어라..그만두지 말고 조금이라도 숨을 쉴 수 있는 공간을 매니저님과 꼭 상의해봐라. 나는 너무 너무 힘들었따,,,너무 못하니카,,그리고 배우는 것도 느리니까,, 근데 혼자 고민할 필요 없당. 다 비슷비슷하게 생각한다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발...................../TIL</category>
      <category>내일배움캠프 #국비지원부트캠프 #내일배움캠프후기 #부트캠프솔직후기 #개발자부트캠프</category>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/119</guid>
      <comments>https://bluewiper.tistory.com/119#entry119comment</comments>
      <pubDate>Mon, 7 Oct 2024 10:07:33 +0900</pubDate>
    </item>
    <item>
      <title>시뮬레이터 안 돌리고 빌드 컴파일 하는 방법</title>
      <link>https://bluewiper.tistory.com/116</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;cmd + B&amp;nbsp;&lt;/p&gt;</description>
      <category>개발...................../TIL</category>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/116</guid>
      <comments>https://bluewiper.tistory.com/116#entry116comment</comments>
      <pubDate>Tue, 23 Jul 2024 15:11:10 +0900</pubDate>
    </item>
    <item>
      <title>테이블뷰 기본</title>
      <link>https://bluewiper.tistory.com/112</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bswkF2/btsIEnyImWN/swQdZDnRM0kxz0APFXTKP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bswkF2/btsIEnyImWN/swQdZDnRM0kxz0APFXTKP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bswkF2/btsIEnyImWN/swQdZDnRM0kxz0APFXTKP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbswkF2%2FbtsIEnyImWN%2FswQdZDnRM0kxz0APFXTKP1%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;342&quot; height=&quot;741&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1721349741998&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;swift&quot;&gt;&lt;code&gt;import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var data: [String] = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;, &quot;Date&quot;, &quot;Elderberry&quot;, &quot;Fig&quot;, &quot;Grape&quot;, &quot;Honeydew&quot;]
    
    let myTableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .green
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: &quot;Cell&quot;)
        return tableView
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        myTableView.dataSource = self
        myTableView.delegate = self
        
        view.addSubview(myTableView)
        
        configureTableView()
                
    }
    
    func configureTableView() {
        
        NSLayoutConstraint.activate([
            myTableView.topAnchor.constraint(equalTo: view.topAnchor),
            myTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            myTableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            myTableView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
    }
    
    // MARK: - UITableViewDataSource Methods
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        
        data.count
    }
    
    // MARK: - UITableViewDelegate Methods
    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.row]

        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -&amp;gt; CGFloat {
       
        60
    }
    
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // 데이터 소스 항목에서 삭제
            data.remove(at: indexPath.row)
            // 테이블 뷰 행에서 삭제
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. UI 인스턴스 생성 및 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰 인스턴스를 생성해야 이후에 사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721349462321&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let tableView: UITableView = {
    let tableView = UITableView()
    return tableView
}()&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;2. 오토레이아웃 비활성화 [UI요소].translatesAutoresizingMaskIntoConstraints&amp;nbsp;=&amp;nbsp;false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오토레이아웃을 사용할 것이므로 자동으로 생성되는 제약을 비활성화해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721349485537&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tableView.translatesAutoresizingMaskIntoConstraints = false&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;3. 서브뷰에 추가 view.addsubView&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰를 뷰 계층 구조에 추가해야 화면에 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;view.addSubview(tableView)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. register&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰에서 사용할 셀을 등록해야 테이블뷰가 셀을 재사용할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721349525006&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tableView.register(UITableViewCell.self, forCellReuseIdentifier: &quot;cell&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. delegate, dataSource 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블뷰의 델리게이트와 데이터 소스를 설정해야 테이블뷰가 데이터를 표시하고 사용자 상호작용을 처리할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721349539086&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tableView.dataSource = self
tableView.delegate = self&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;6. numberOfRowsInSection, cellForRowAt&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UITableViewDataSource 메서드를 구현해야 테이블뷰가 몇 개의 셀을 표시할지와 각 셀의 내용을 결정할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1721349566407&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
    return data.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.row]
    return cell
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발...................../TIL</category>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/112</guid>
      <comments>https://bluewiper.tistory.com/112#entry112comment</comments>
      <pubDate>Fri, 19 Jul 2024 09:27:24 +0900</pubDate>
    </item>
    <item>
      <title>베이직반 실습</title>
      <link>https://bluewiper.tistory.com/106</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QzFBL/btsIAr1BDIZ/As0Di4FbgwyXxzj8wwIhC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QzFBL/btsIAr1BDIZ/As0Di4FbgwyXxzj8wwIhC0/img.png&quot; data-alt=&quot;UI section 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QzFBL/btsIAr1BDIZ/As0Di4FbgwyXxzj8wwIhC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQzFBL%2FbtsIAr1BDIZ%2FAs0Di4FbgwyXxzj8wwIhC0%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;260&quot; height=&quot;564&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UI section 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIsection 1&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1721051709543&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 {
    
    // UI 요소
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;Select your coffee:&quot;
        label.font = .boldSystemFont(ofSize: 25)
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let segmentedControl: UISegmentedControl = {
        let segmentedControl = UISegmentedControl(items: [&quot;Espresso - $3&quot;, &quot;Latte - $4&quot;, &quot;Cappuccino - $5&quot;])
        segmentedControl.selectedSegmentIndex = 0
        segmentedControl.translatesAutoresizingMaskIntoConstraints = false
        return segmentedControl
    }()
    
    private let quantityLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;Enter quantity:&quot;
        label.font = .boldSystemFont(ofSize: 25)
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let textField: UITextField = {
        let textField = UITextField()
        textField.placeholder = &quot;수량을 입력해주세요.&quot;
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private lazy var orderButton: UIButton = {
        let button = UIButton()
        button.setTitle(&quot;order&quot;, for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        return button
    }()
    
    private let orderedLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;You orderd 3 Espresso(s). Total: $9&quot; // 추후 바꾸기
        label.textAlignment = .center
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        // 뷰 생성
        [
            titleLabel,
            segmentedControl,
            quantityLabel,
            textField,
            orderButton,
            orderedLabel
        ].forEach { view.addSubview($0) }
        
        
        configureUI()
        
    }
    private func configureUI() {
        
        
        
        // 제약 조건
        NSLayoutConstraint.activate([
            
            // 타이틀 라벨 레이아웃
            titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            
            // 세그먼트 컨트롤 레이아웃
            segmentedControl.heightAnchor.constraint(equalToConstant: 40),
            segmentedControl.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 25),
            segmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            
            // 수량 라벨 레이아웃
            quantityLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            quantityLabel.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 40),
            
            // 주문 수량 텍스트 필드 레이아웃
            textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            textField.topAnchor.constraint(equalTo: quantityLabel.bottomAnchor, constant: 20),
            textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            
            // 주문 버튼 레이아웃
            orderButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            orderButton.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 50),
            
            // 주문 결과 라벨 레이아웃
            orderedLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            orderedLabel.topAnchor.constraint(equalTo: orderButton.bottomAnchor, constant: 50),
            orderedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
        ])
        
        
    }
    
    // order button을 눌렀을 때 액션과 이벤트를 담당하는 메서드
    // 메뉴당 단가, 선택한 메뉴, 총 금액(메뉴당 단가 * 입력한 수량)
    @objc
    private func buttonTapped() {
        
        // 각 메뉴의 단가 배열
        let prices = [3, 4, 5]
        
        
        // 선택된 세그먼트의 인덱스를 가져와서 해당하는 메뉴의 단가를 가져옴
        let selectedMenuIndex = segmentedControl.selectedSegmentIndex
        
        // 선택된 커피 : 옵셔널 체이닝이나 널 병합 연산자라고 불리며, 옵셔널 값이 nil일 경우 대체할 기본 값을 설정
        let selectedMenu = segmentedControl.titleForSegment(at: selectedMenuIndex)?.components(separatedBy: &quot; - &quot;).first ?? &quot;Unknown&quot;
        
        let menuPrice = prices[selectedMenuIndex]
        
        // 텍스트 필드에서 입력된 수량 가져오기
        guard let quantityText = textField.text, let quantity = Int(quantityText) else {
            return
        }
        
        // totalPrice 구하기
        let totalPrice = menuPrice * quantity

        
        // buttonTapped 후 이벤트로 결과값 문구 출력
        
        // 디버깅용
        print(&quot;You ordered \(quantity) \(selectedMenu)(s). Total Price: $\(totalPrice).&quot;)
        
        // 화면에 띄우기
        orderedLabel.text = &quot;You ordered \(quantity) \(selectedMenu)(s). Total Price: $\(totalPrice).&quot;
        
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜터님&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1721044317050&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController {
  // UI 요소 선언
  let coffeeLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Select your coffee:&quot;
    label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
    label.textAlignment = .center
    return label
  }()
  let coffeeSegmentedControl: UISegmentedControl = {
    let items = [&quot;Espresso - $3&quot;, &quot;Latte - $4&quot;, &quot;Cappuccino - $5&quot;]
    let segmentedControl = UISegmentedControl(items: items)
    segmentedControl.translatesAutoresizingMaskIntoConstraints = false
    segmentedControl.selectedSegmentIndex = 0
    return segmentedControl
  }()
  let quantityLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Enter quantity:&quot;
    label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
    label.textAlignment = .center
    return label
  }()
  let quantityTextField: UITextField = {
    let textField = UITextField()
    textField.translatesAutoresizingMaskIntoConstraints = false
    textField.placeholder = &quot;Quantity&quot;
    textField.borderStyle = .roundedRect
    textField.keyboardType = .numberPad
    return textField
  }()
  let orderButton: UIButton = {
    let button = UIButton(type: .system)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle(&quot;Order&quot;, for: .normal)
    button.addTarget(self, action: #selector(orderButtonTapped), for: .touchUpInside)
    return button
  }()
  let resultLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Your order will appear here.&quot;
    label.textAlignment = .center
    label.numberOfLines = 0
    return label
  }()
  override func viewDidLoad() {
    super.viewDidLoad()
    // 뷰 설정
    view.backgroundColor = .white
    // 서브 뷰 추가
    view.addSubview(coffeeLabel)
    view.addSubview(coffeeSegmentedControl)
    view.addSubview(quantityLabel)
    view.addSubview(quantityTextField)
    view.addSubview(orderButton)
    view.addSubview(resultLabel)
    // 레이아웃 설정
    setupLayout()
  }
  func setupLayout() {
    NSLayoutConstraint.activate([
      // 커피 레이블 레이아웃
      coffeeLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
      coffeeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      // 세그먼트 컨트롤 레이아웃
      coffeeSegmentedControl.topAnchor.constraint(equalTo: coffeeLabel.bottomAnchor, constant: 20),
      coffeeSegmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      coffeeSegmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      // 수량 레이블 레이아웃
      quantityLabel.topAnchor.constraint(equalTo: coffeeSegmentedControl.bottomAnchor, constant: 20),
      quantityLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      // 수량 텍스트 필드 레이아웃
      quantityTextField.topAnchor.constraint(equalTo: quantityLabel.bottomAnchor, constant: 10),
      quantityTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      quantityTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
      // 주문 버튼 레이아웃
      orderButton.topAnchor.constraint(equalTo: quantityTextField.bottomAnchor, constant: 20),
      orderButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      // 결과 레이블 레이아웃
      resultLabel.topAnchor.constraint(equalTo: orderButton.bottomAnchor, constant: 20),
      resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
    ])
  }
  @objc func orderButtonTapped() {
    let coffeePrices = [3, 4, 5]
    let selectedCoffeeIndex = coffeeSegmentedControl.selectedSegmentIndex
    let selectedCoffee = coffeeSegmentedControl.titleForSegment(at: selectedCoffeeIndex)?.components(separatedBy: &quot; - &quot;).first ?? &quot;Unknown&quot;
    let pricePerCoffee = coffeePrices[selectedCoffeeIndex]
    guard let quantityText = quantityTextField.text, let quantity = Int(quantityText), quantity &amp;gt; 0 else {
      resultLabel.text = &quot;Please enter a valid quantity.&quot;
      return
    }
    let totalPrice = pricePerCoffee * quantity
    resultLabel.text = &quot;You ordered \(quantity) \(selectedCoffee)(s). Total: $\(totalPrice).&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UIsection 2까지 구현&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D62dt/btsIy5E97m9/JghauEqnJRFZzmcW7icK4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D62dt/btsIy5E97m9/JghauEqnJRFZzmcW7icK4K/img.png&quot; data-alt=&quot;UI section 2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D62dt/btsIy5E97m9/JghauEqnJRFZzmcW7icK4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD62dt%2FbtsIy5E97m9%2FJghauEqnJRFZzmcW7icK4K%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;232&quot; height=&quot;503&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;UI section 2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1721051729053&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
//  ViewController.swift
//  basicUIPractice
//
//  Created by 단예진 on 7/9/24.
//


import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    // UI 요소
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;Select your coffee:&quot;
        label.font = .boldSystemFont(ofSize: 25)
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let segmentedControl: UISegmentedControl = {
        let segmentedControl = UISegmentedControl(items: [&quot;Espresso - $3&quot;, &quot;Latte - $4&quot;, &quot;Cappuccino - $5&quot;])
        segmentedControl.selectedSegmentIndex = 0
        segmentedControl.translatesAutoresizingMaskIntoConstraints = false
        return segmentedControl
    }()
    
    private let quantityLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;Enter quantity:&quot;
        label.font = .boldSystemFont(ofSize: 25)
        label.textColor = .black
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private let textField: UITextField = {
        let textField = UITextField()
        textField.placeholder = &quot;수량을 입력해주세요.&quot;
        textField.borderStyle = .roundedRect
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()
    
    private lazy var orderButton: UIButton = {
        let button = UIButton()
        button.setTitle(&quot;order&quot;, for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        return button
    }()
    
    //    private let orderedLabel: UILabel = {
    //        let label = UILabel()
    //        label.text = &quot;You orderd 3 Espresso(s). Total: $9&quot; // 추후 바꾸기
    //        label.textAlignment = .center
    //        label.translatesAutoresizingMaskIntoConstraints = false
    //        return label
    //    }()
    
    private let tableView: UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: &quot;orderCell&quot;)
        return tableView
    }()
    
    private let totalLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;Total: $0&quot;
        label.textColor = .black
        label.textAlignment = .center
        label.font = .boldSystemFont(ofSize: 30)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    private var orders: [(coffee: String, quantity: Int, price: Int)] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        // 테이블뷰 델리게이트 및 데이터소스 설정
        tableView.delegate = self
        tableView.dataSource = self
        
        // 뷰 생성
        [
            titleLabel,
            segmentedControl,
            quantityLabel,
            textField,
            orderButton,
            //            orderedLabel,
            tableView,
            totalLabel
        ].forEach { view.addSubview($0) }
        
        
        configureUI()
        
    }
    private func configureUI() {
        
        
        
        NSLayoutConstraint.activate([
                // titleLabel 레이아웃
                titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
                
                // segmentedControl 레이아웃
                segmentedControl.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
                segmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                segmentedControl.widthAnchor.constraint(equalToConstant: 300),
                
                // quantityLabel 레이아웃
                quantityLabel.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 40),
                quantityLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                
                // textField 레이아웃
                textField.topAnchor.constraint(equalTo: quantityLabel.bottomAnchor, constant: 20),
                textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
                textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
                
                // orderButton 레이아웃
                orderButton.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 50),
                orderButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                
                // tableView 레이아웃
                tableView.topAnchor.constraint(equalTo: orderButton.bottomAnchor, constant: 20),
                tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                tableView.bottomAnchor.constraint(equalTo: totalLabel.topAnchor, constant: -20),
                
                // totalLabel 레이아웃
                totalLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                totalLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20)
            ])
        
        
    }
    
    // order button을 눌렀을 때 액션과 이벤트를 담당하는 메서드
    // 메뉴당 단가, 선택한 메뉴, 총 금액(메뉴당 단가 * 입력한 수량)
    @objc
    private func buttonTapped() {
        
        guard let quantityText = textField.text, let quantity = Int(quantityText) else {
            print(&quot;수량이 올바르게 입력되지 않았습니다.&quot;)
            return
        }
        
        
        
        // 각 메뉴의 단가 배열
        let prices = [3, 4, 5]
        
        
        // 선택된 세그먼트의 인덱스를 가져와서 해당하는 메뉴의 단가를 가져옴
        let selectedMenuIndex = segmentedControl.selectedSegmentIndex
        
        // 선택된 커피 : 옵셔널 체이닝이나 널 병합 연산자라고 불리며, 옵셔널 값이 nil일 경우 대체할 기본 값을 설정
        let selectedMenu = segmentedControl.titleForSegment(at: selectedMenuIndex)?.components(separatedBy: &quot; - &quot;).first ?? &quot;Unknown&quot;
        
        let menuPrice = prices[selectedMenuIndex]
        
        // 텍스트 필드에서 입력된 수량 가져오기
        guard let quantityText = textField.text, let quantity = Int(quantityText) else {
            return
        }
        
        // totalPrice 구하기
        let totalPrice = menuPrice * quantity
        
        // 주문 내역에 추가
        let order = (coffee: selectedMenu, quantity: quantity, price: totalPrice)
        orders.append(order)
        
        // buttonTapped 후 이벤트로 결과값 문구 출력
        
        // 디버깅용
        print(&quot;You ordered \(quantity) \(selectedMenu)(s). Total Price: $\(totalPrice).&quot;)
        
        // 화면에 띄우기
        //        orderedLabel.text = &quot;You ordered \(quantity) \(selectedMenu)(s). Total Price: $\(totalPrice).&quot;
        
        // 테이블뷰 데이터 리로드 및 총금액 업데이트
        tableView.reloadData()
        updateTotalLabel()
        
    }
    
    func updateTotalLabel() {
        let totalAmount = orders.reduce(0) { $0 + $1.price }
        totalLabel.text = &quot;Total: $\(totalAmount)&quot;
    }
    
    // 테이블뷰 데이터 소스 메서드
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        return orders.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: &quot;orderCell&quot;, for: indexPath)
        let order = orders[indexPath.row]
        cell.textLabel?.text = &quot;\(order.quantity) x \(order.coffee) - $\(order.price)&quot;
        return cell
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;튜터님&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1721044273642&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
   
  // UI 요소 선언
  let coffeeLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Select your coffee:&quot;
    label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
    label.textAlignment = .center
    return label
  }()
   
  let coffeeSegmentedControl: UISegmentedControl = {
    let items = [&quot;Espresso - $3&quot;, &quot;Latte - $4&quot;, &quot;Cappuccino - $5&quot;]
    let segmentedControl = UISegmentedControl(items: items)
    segmentedControl.translatesAutoresizingMaskIntoConstraints = false
    segmentedControl.selectedSegmentIndex = 0
    return segmentedControl
  }()
   
  let quantityLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Enter quantity:&quot;
    label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
    label.textAlignment = .center
    return label
  }()
   
  let quantityTextField: UITextField = {
    let textField = UITextField()
    textField.translatesAutoresizingMaskIntoConstraints = false
    textField.placeholder = &quot;Quantity&quot;
    textField.borderStyle = .roundedRect
    textField.keyboardType = .numberPad
    return textField
  }()
   
  let orderButton: UIButton = {
    let button = UIButton(type: .system)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle(&quot;Order&quot;, for: .normal)
    button.addTarget(self, action: #selector(orderButtonTapped), for: .touchUpInside)
    return button
  }()
   
  let tableView: UITableView = {
    let tableView = UITableView()
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: &quot;orderCell&quot;)
    return tableView
  }()
   
  let totalLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = &quot;Total: $0&quot;
    label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
    label.textAlignment = .center
    return label
  }()
   
  var orders: [(coffee: String, quantity: Int, price: Int)] = []
   
  override func viewDidLoad() {
    super.viewDidLoad()
     
    // 뷰 설정
    view.backgroundColor = .white
     
    // 테이블 뷰 델리게이트 및 데이터 소스 설정
    tableView.delegate = self
    tableView.dataSource = self
     
    // 서브 뷰 추가
    view.addSubview(coffeeLabel)
    view.addSubview(coffeeSegmentedControl)
    view.addSubview(quantityLabel)
    view.addSubview(quantityTextField)
    view.addSubview(orderButton)
    view.addSubview(tableView)
    view.addSubview(totalLabel)
     
    // 레이아웃 설정
    setupLayout()
  }
   
  func setupLayout() {
    NSLayoutConstraint.activate([
      // 커피 레이블 레이아웃
      coffeeLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
      coffeeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
       
      // 세그먼트 컨트롤 레이아웃
      coffeeSegmentedControl.topAnchor.constraint(equalTo: coffeeLabel.bottomAnchor, constant: 10),
      coffeeSegmentedControl.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      coffeeSegmentedControl.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
       
      // 수량 레이블 레이아웃
      quantityLabel.topAnchor.constraint(equalTo: coffeeSegmentedControl.bottomAnchor, constant: 20),
      quantityLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
       
      // 수량 텍스트 필드 레이아웃
      quantityTextField.topAnchor.constraint(equalTo: quantityLabel.bottomAnchor, constant: 10),
      quantityTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
      quantityTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
       
      // 주문 버튼 레이아웃
      orderButton.topAnchor.constraint(equalTo: quantityTextField.bottomAnchor, constant: 20),
      orderButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
       
      // 테이블 뷰 레이아웃
      tableView.topAnchor.constraint(equalTo: orderButton.bottomAnchor, constant: 20),
      tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      tableView.bottomAnchor.constraint(equalTo: totalLabel.topAnchor, constant: -10),
       
      // 총 금액 레이블 레이아웃
      totalLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
      totalLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    ])
  }
   
  @objc func orderButtonTapped() {
    let coffeePrices = [3, 4, 5]
    let selectedCoffeeIndex = coffeeSegmentedControl.selectedSegmentIndex
    let selectedCoffee = coffeeSegmentedControl.titleForSegment(at: selectedCoffeeIndex)?.components(separatedBy: &quot; - &quot;).first ?? &quot;Unknown&quot;
    let pricePerCoffee = coffeePrices[selectedCoffeeIndex]
     
    guard let quantityText = quantityTextField.text, let quantity = Int(quantityText), quantity &amp;gt; 0 else {
      showAlert(message: &quot;Please enter a valid quantity.&quot;)
      return
    }
     
    let totalPrice = pricePerCoffee * quantity
    orders.append((coffee: selectedCoffee, quantity: quantity, price: totalPrice))
     
    tableView.reloadData()
    updateTotalLabel()
  }
   
  func showAlert(message: String) {
    let alert = UIAlertController(title: &quot;Error&quot;, message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: &quot;OK&quot;, style: .default, handler: nil))
    present(alert, animated: true, completion: nil)
  }
   
  func updateTotalLabel() {
    let totalAmount = orders.reduce(0) { $0 + $1.price }
    totalLabel.text = &quot;Total: $\(totalAmount)&quot;
  }
   
  // 테이블 뷰 데이터 소스 메서드
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
    return orders.count
  }
   
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: &quot;orderCell&quot;, for: indexPath)
    let order = orders[indexPath.row]
    cell.textLabel?.text = &quot;\(order.quantity) x \(order.coffee) - $\(order.price)&quot;
    return cell
  }
   
  // 테이블 뷰 델리게이트 메서드
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/106</guid>
      <comments>https://bluewiper.tistory.com/106#entry106comment</comments>
      <pubDate>Mon, 15 Jul 2024 19:14:47 +0900</pubDate>
    </item>
    <item>
      <title>날씨앱만들기 (4) 5일간의 날씨데이터로 구성된 테이블뷰</title>
      <link>https://bluewiper.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;(이전 게시물)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 날씨 이미지 아래에 들어갈 5일간의 날씨데이터로 구성된 테이블뷰에 대해 작업해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n4FvP/btsIAVupiTt/JKSPFhhPDXSjSJo8xaoJxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n4FvP/btsIAVupiTt/JKSPFhhPDXSjSJo8xaoJxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n4FvP/btsIAVupiTt/JKSPFhhPDXSjSJo8xaoJxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn4FvP%2FbtsIAVupiTt%2FJKSPFhhPDXSjSJo8xaoJxk%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;394&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 사용할 API는 openWeather에서 forecastWeather API이다.&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;기존에 프로퍼티들을 등록한 곳에 tableView도 하나 만들어준다.&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;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721030050122&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //5일간의 날씨데이터를 담을 테이블뷰
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .black
        // delegate: &quot;대리자, 대신 수행 해주는 사람&quot;, Table View의 여러가지 속성 세팅을 이 View Controller에서 대신 세팅하는 코드를 작성해주겠다.
        tableView.delegate = self
        // dateSource: 테이블뷰 안에 집어넣을 데이터들, 이 View Controller에서 세팅해주겠다.
        tableView.dataSource = self
    }()&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;tableView는 delegate와 dataSource를 세팅해줘야 온전히 구현할 수 있다.&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;처음에는 Private let으로 작성했지만 오류가 난다. 이유는, 위 코드는 UI 초기화 설정단계이기 때문에 ViewController가 완성되지 않았기 때문이라고 한다. 그래서 private lazy var로 작성해준다.&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;위까지 작성하게 되면 tableView delegate 세팅하지 않았다는 오류 메시지가 뜬다.&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;View Controller class 밖에서 extension으로 View Controller가 TableViewDelegate를 만족할 수 있도록 코드를 작성해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721030372190&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension ViewController: UITableViewDelegate {
    // 테이블뷰 셀 높이 크기 지정
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -&amp;gt; CGFloat {
        40
    }
}&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;TableViewDataSource도 마찬가지로 코드를 작성해줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721030416301&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension ViewController: UITableViewDataSource {
    // 테이블 뷰의 indexPath마다 테이블 뷰 셀을 지정
    // indextPath는 테이블뷰의 행과 섹션을 의미
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -&amp;gt; UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.id) as? TableViewCell else {
            return UITableViewCell()
        }
        cell.configureCell(forecastWeather: dataSource[indexPath.row])
        return cell
        }

    // 테이블 뷰 섹션에 행이 몇 개 들어가는가. 여기서 섹션은 없으니 총 행 개수만 입력하면 된다.
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -&amp;gt; Int {
        dataSource.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;UITableViewDataSource 안에는 반드시&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cellForRowAt과 NumberOfRowsInSection메서드를 포함해야 한다. (주석 참고)&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;이제 TableViewCell로 가서 정의해준다.&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;TableViewCell.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721030626586&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import SnapKit

final class TableViewCell: UITableViewCell {
    
    static let id = &quot;TableViewCell&quot;
    
    private let dtTxtLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .black
        label.textColor = .white
        return label
    }()
    
    private let tempLabel: UILabel = {
        let label = UILabel()
        label.backgroundColor = .black
        label.textColor = .white
        return label
    }()
    // Table View의 style과 id로 초기화할 때 사용하는 코드
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        configureUI()
    }
    // 인터페이스 빌더를 통해 셀을 초기화할 때 사용하는 코드
    // 여기서는 fatalError를 통해서 명시적으로 인터페이스 빌더로 초기화하지 않음을 나타냄
    required init?(coder: NSCoder) {
        fatalError(&quot;init(coder:) has not been implemented&quot;)
    }
    
    private func configureUI() {
        
        contentView.backgroundColor = .black
        
        [
            dtTxtLabel,
            tempLabel
        ].forEach { contentView.addSubview($0) }
        
        dtTxtLabel.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.leading.equalToSuperview().inset(20)
        }
        
        tempLabel.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.trailing.equalToSuperview().inset(20)
        }
    }
    
    public func configureCell(forecastWeather: ForecastWeather) {
        dtTxtLabel.text = &quot;\(forecastWeather.dtTxt)&quot;
        tempLabel.text = &quot;\(forecastWeather.main.temp)&amp;deg;C&quot;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 configureUI()를 만들어 놓고 초기화 공간에서 호출하지 않아서 하얀 테이블뷰만 나왔었다.. ㅎㅎ&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 class를 생성할 때는 override init과 required init을 모두 써줘야 한다. (주석 참고)&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는 작업이 어느정도 되었기 때문에&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;ForecastWeatherData에다가 서버에서 가져올 데이터 형식에 대해 정의해준다.&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;ForecastWeatherResult.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721030939568&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation


//Codable을 채택하는 구조체 생성
struct ForecastWeatherResult: Codable {
    let list: [ForecastWeather]
    
}

struct ForecastWeather: Codable {
    let main: WeatherMain
    let dtTxt: String
    
    enum CodingKeys: String, CodingKey {
        case main
        case dtTxt = &quot;dt_txt&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 마찬가지로 openWeather에서는 dtTxt에 대해 스네이크 표기법을 쓰고 있기 때문에 camel 형식을 쓰는 Swift에서 한번에 JSON Decoding이 안 되기 때문에 파싱이 되도록 enum과 case로 코드를 입력해준다.&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;currentWeather 때와 마찬가지로 fetchData메서드를 통해 서버데이터를 불러와준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721031228028&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서버에서 5일 간 날씨 데이터를 불러오는 메서드
    private func fetchForecastData() {
        var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/forecast&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 URL&quot;)
            return
        }
        
        fetchData(url: url) { [weak self] (result: ForecastWeatherResult?) in
            guard let self, let result else { return }
            
            // 콘솔에다가 데이터 잘 불러왔는지 찍어보기
            for forecastWeather in result.list {
                print(&quot;\(forecastWeather.main)\n\(forecastWeather.dtTxt)\n\n&quot;)
            }
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData() // 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;마찬가지로 url을 불러올 때 쿼리 이전까지 불러온다. 쿼리아이템은 위에서 선언해준 것을 재활용한다. (url 바로 뒤 코드 참고)&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;위 코드를 보면 콘솔에다가 데이터를 잘 불러왔는지 확인할 수 있도록 DataSource를 선언해준 것을 불러온다.&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;아래는 DataSource 선언해준 코드이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721031442625&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //데이터소스 타입은 항상 리스트
    private var dataSource = [ForecastWeather]()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업해준 테이블뷰가 보일 수 있도록&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;테이블뷰의 뷰생성과, 제약조건이 포함된 configureUI 메서드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721031850755&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func configureUI() {
        view.backgroundColor = .black
        
        //각 프로퍼티 뷰 생성
        [
            titleLabel,
            tempLabel,
            tempStackView,
            imageView,
            tableView
        ].forEach { view.addSubview($0) }
        
        //tempStackView에 들어갈 최저기온과 최고기온 뷰 추가
        [
            tempMinLabel,
            tempMaxLabel
        ].forEach { tempStackView.addArrangedSubview($0) }
        
        //각 프로퍼티 제약 조건
        titleLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalToSuperview().offset(120)
        }
        
        tempLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(titleLabel.snp.bottom).offset(10)
        }
        
        tempStackView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(tempLabel.snp.bottom).offset(10)
        }
        
        imageView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.height.equalTo(160)
            $0.top.equalTo(tempStackView.snp.bottom).offset(20)
        }
        
        tableView.snp.makeConstraints {
            $0.top.equalTo(imageView.snp.bottom).offset(30)
            $0.leading.trailing.equalToSuperview().inset(20)
            $0.bottom.equalToSuperview().inset(50)
        }
        
    }
}&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;forecastWeather에 대해서도 fetchData 작업을 끝냈으니 viewDidLoad에서 호출해준다.&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;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721032024658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //5일간의 날씨데이터를 담을 테이블뷰
    private lazy var tableView: UITableView = {
        let tableView = UITableView()
        tableView.backgroundColor = .black
        // delegate: &quot;대리자, 대신 수행 해주는 사람&quot;, Table View의 여러가지 속성 세팅을 이 View Controller에서 대신 세팅하는 코드를 작성해주겠다.
        tableView.delegate = self
        // dateSource: 테이블뷰 안에 집어넣을 데이터들, 이 View Controller에서 세팅해주겠다.
        tableView.dataSource = self
        // 테이블뷰에다가 테이블 뷰 셀 등록
        tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.id)
        return tableView
    }()&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;실행하면 콘솔창에서도 데이터 로드가 확인되고, UI도 원하는대로 구성된 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/105</guid>
      <comments>https://bluewiper.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 15 Jul 2024 17:28:06 +0900</pubDate>
    </item>
    <item>
      <title>날씨앱만들기 (3) UI 구성 2</title>
      <link>https://bluewiper.tistory.com/104</link>
      <description>&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;figure id=&quot;og_1721029713930&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;날씨앱만들기 (2) UI 구성 1&quot; data-og-description=&quot;일단은 UI 구성이 잘 되었는지 확인이 필요하니까 임의의 컬러, 텍스트값을 넣어서 UI 구성을 해준다.&amp;nbsp;더보기import UIKitimport SnapKitclass ViewController: UIViewController { //현재 위치한 도시를 알려주는 라&quot; data-og-host=&quot;bluewiper.tistory.com&quot; data-og-source-url=&quot;https://bluewiper.tistory.com/101&quot; data-og-url=&quot;https://bluewiper.tistory.com/101&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eaC9m0/hyWztdEgrj/UrG4tUzrWUkSPdNyipWln0/img.png?width=800&amp;amp;height=1648&amp;amp;face=0_0_800_1648,https://scrap.kakaocdn.net/dn/bpxxSg/hyWzzroYrM/zLDSnq5PU9tqdmD67yRJhK/img.png?width=800&amp;amp;height=1648&amp;amp;face=0_0_800_1648&quot;&gt;&lt;a href=&quot;https://bluewiper.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bluewiper.tistory.com/101&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eaC9m0/hyWztdEgrj/UrG4tUzrWUkSPdNyipWln0/img.png?width=800&amp;amp;height=1648&amp;amp;face=0_0_800_1648,https://scrap.kakaocdn.net/dn/bpxxSg/hyWzzroYrM/zLDSnq5PU9tqdmD67yRJhK/img.png?width=800&amp;amp;height=1648&amp;amp;face=0_0_800_1648');&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;날씨앱만들기 (2) UI 구성 1&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;일단은 UI 구성이 잘 되었는지 확인이 필요하니까 임의의 컬러, 텍스트값을 넣어서 UI 구성을 해준다.&amp;nbsp;더보기import UIKitimport SnapKitclass ViewController: UIViewController { //현재 위치한 도시를 알려주는 라&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bluewiper.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 서버에서 현재 날씨 데이터를 불러오는 메서드를 작성해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 ViewController인데 여기서 함수만 일단 만들어주고, 미리 만들어 놓은 CurrentWeatherData 화면에서 작업을 이어간다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720693773272&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
        
    }&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;이전에 API 데이터를 불러왔을 때 어떤 형태로 받았는지 확인하고, 실질적으로 사용할 만한 데이터의 값을 추리고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 만들어둔 CurrentWeatherResult에 해당 데이터 형태에 맞는 구조체를 만들어 준다.&amp;nbsp;&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;아래는 API 사이트에서 제공하는 API response 예시이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7jaMq/btsIxjV0SgG/gcCxDFuoly2lxY0Qkx0cgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7jaMq/btsIxjV0SgG/gcCxDFuoly2lxY0Qkx0cgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7jaMq/btsIxjV0SgG/gcCxDFuoly2lxY0Qkx0cgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7jaMq%2FbtsIxjV0SgG%2FgcCxDFuoly2lxY0Qkx0cgk%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;3024&quot; height=&quot;1644&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1644&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;이중에서 weather, main 부분만 쓸 예정이다.&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;CurrentWeatherResult.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721026073909&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

//Codable을 채택하는 구조체 생성
struct CurrentWeatherResult: Codable {
    let weather: [Weather]
    let main: WeatherMain
}

struct Weather: Codable {
    let id: Int
    let main: String
    let description: String
    let icon: String
}

struct WeatherMain: Codable {
    let temp: Double
    let tempMin: Double
    let tempMax: Double
    
    enum CodingKeys: String, CodingKey {
        case temp
        case tempMin = &quot;temp_min&quot;
        case tempMax = &quot;temp_max&quot;
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시&lt;b&gt; private func&lt;/b&gt; fetchCurrentWeatherData()로 돌아가서 작성을 이어간다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721026224784&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
       var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 위에서 urlComponent를 살펴보면 query 이전까지의 url만 복사해서 불러왔다.&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;currentWeather, forecastWeather의 url 커리가 똑같이 사용되고 있어서 각각 적어주지 않고, 불러올 수 있도록 쿼리에 대한 프로퍼티를 생성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API call을 확인해서 필요한 값을 파악한다. 우리는 위치를 확인하기 위해 lat, lon이 필요하고, api key를 넣을 수 있도록&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;appid, 그리고 metric 시스템을 적용하기 위해 units를 쿼리아이템으로 만들어줄 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7L6Le/btsIzp4dzU8/OFmuHCGzWEDkd0YfSyuNX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7L6Le/btsIzp4dzU8/OFmuHCGzWEDkd0YfSyuNX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7L6Le/btsIzp4dzU8/OFmuHCGzWEDkd0YfSyuNX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7L6Le%2FbtsIzp4dzU8%2FOFmuHCGzWEDkd0YfSyuNX1%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;3024&quot; height=&quot;1278&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1278&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.swift&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티들이 모여있는 곳에 정의해줬다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721026462147&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //URL 쿼리아이템들(Current Weather, Forcast Weather 두가지이지만 똑같은 쿼리를 쓰는 URL을 쓰기 때문에 미리 정의
    //서울역 위경도
    private let urlQueryItems: [URLQueryItem] = [
        URLQueryItem(name: &quot;lat&quot;, value: &quot;37.5&quot;),
        URLQueryItem(name: &quot;lon&quot;, value: &quot;126.9&quot;),
    URLQueryItem(name: &quot;appid&quot;, value: &quot;dd36828ea83a2a55cfdfab1d88773876&quot;),
    URLQueryItem(name: &quot;units&quot;, value: &quot;metric&quot;)
    ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController.swift&lt;/p&gt;
&lt;pre id=&quot;code_1721026717366&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
       var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 url&quot;)
            return
        } 
        
        fetchData(url: url) { [weak self] (result: CurrentWeatherResult?) in
            guard let self, let result else { return }
            
            // 메인스레드에서 작동하도록 하는 메서드
            DispatchQueue.main.async {
                self.tempLabel.text = &quot;\(Int(result.main.temp))&amp;deg;C&quot; // 서버에서 가져온 현재 날씨 데이터가 불러와짐
                self.tempMinLabel.text = &quot;최저: \(Int(result.main.tempMin))&amp;deg;C&quot;
                self.tempMaxLabel.text = &quot;최고: \(Int(result.main.tempMax))&amp;deg;C&quot;
            }
            guard let imageUrl = URL(string: &quot;https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png&quot;) else {
                return
            }
            
            // image를 로드하는 작업은 백그라운드 쓰레드 작업
            if let data = try? Data(contentsOf: imageUrl) {
                if let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self.imageView.image = image
                    }
                }
            }
        }
    }&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;b&gt;* Notice&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버데이터를 불러오는 작업은 백그라운드 스레드에서 실행되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 작업은 메인 스레드에서 해야하기 때문에 DispatchQuene.main.async{} 코드 안에서 UI 작업을 해준다.&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;이렇게 작업해주면 기존에 tempLabel 등의 text를 임의로 지정해줬던 곳에 서버 데이터가 들어오게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1721029126848&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //기온을 나타내는 라벨
    private let tempLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;20도&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 50)
        return label
    }()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721029164377&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;                DispatchQueue.main.async {
                    self.tempLabel.text = &quot;\(Int(result.main.temp))&amp;deg;C&quot;
                }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 코드가 완료 되면 viewDidLoad에서 호출해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BAqdC/btsIAbEz4sa/TDpNSIh3tXti1XPUFA3kIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BAqdC/btsIAbEz4sa/TDpNSIh3tXti1XPUFA3kIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BAqdC/btsIAbEz4sa/TDpNSIh3tXti1XPUFA3kIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBAqdC%2FbtsIAbEz4sa%2FTDpNSIh3tXti1XPUFA3kIk%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;1280&quot; height=&quot;947&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;947&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이 화면에서 날씨 이미지까지 서버데이터를 통해 구현된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;</description>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/104</guid>
      <comments>https://bluewiper.tistory.com/104#entry104comment</comments>
      <pubDate>Mon, 15 Jul 2024 16:45:37 +0900</pubDate>
    </item>
    <item>
      <title>Alamofire를 이용하여 데이터 불러오기</title>
      <link>https://bluewiper.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Alamofire는&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Swift로 작성된 강력하고 사용하기 쉬운 네트워킹 라이브러리입니다. 주로 iOS 및 macOS 애플리케이션에서 HTTP 네트워킹 작업을 간단하고 직관적으로 수행할 수 있도록 도와줍니다. Alamofire는 다음과 같은 주요 기능을 제공합니다:&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;주요 기능&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTP 요청 및 응답&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET, POST, PUT, DELETE 등의 다양한 HTTP 메서드를 쉽게 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;JSON, plist, XML 등 다양한 형식의 응답 데이터를 자동으로 파싱할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 요청 설정&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL, 매개변수, 헤더 등을 쉽게 구성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;URLSessionConfiguration을 사용하여 네트워크 세션을 커스터마이즈할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 응답, JSON 응답, String 응답 등 다양한 방식으로 응답을 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;응답 검증 및 에러 처리 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일 업로드 및 다운로드&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 리치성(Richness)&lt;/b&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;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alamofire는 네트워킹 요청의 응답을 처리할 때 Result 타입을 사용하여 성공과 실패를 구분합니다.&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;switch 구문을 사용하여 성공과 실패 케이스를 처리하는 방식을 채택하여&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;.success(let data)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 성공하면 response.result는 .success 상태가 되고, data에 응답 데이터가 포함됩니다.&lt;/li&gt;
&lt;li&gt;여기서 data는 JSON 형식일 수 있으며, 이를 파싱하여 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;.failure(let error)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 실패하면 response.result는 .failure 상태가 되고, error에 에러 정보가 포함됩니다.&lt;/li&gt;
&lt;li&gt;네트워크 문제, 서버 응답 문제, 데이터 파싱 문제 등 다양한 이유로 요청이 실패할 수 있습니다.&lt;/li&gt;
&lt;li&gt;여기서 error를 처리하여 사용자에게 오류 메시지를 표시하거나, 재시도 로직을 구현할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 서버데이터를 불러오는 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1721022100498&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서버 데이터를 불러오는 메서드
    private func fetchData&amp;lt;T: Decodable&amp;gt;(url: URL, completion: @escaping (T?) -&amp;gt; Void) {
        let session = URLSession(configuration: .default)
        session.dataTask(with: URLRequest(url: url)) { data, response, error in
            guard let data, error == nil else {
                print(&quot;데이터 로드 실패&quot;)
                completion(nil)
                return
            }
            // http status code 성공 범위는 200번대
            let successRange = 200..&amp;lt;300
            //HTTPURLResponse 안에 http status code를 깔기 위해 타입 캐스팅을 해준다.
            if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
                // 받은 데이터가 JSON 형태일테니 Swift 형태로 디코딩해준다.
                guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else {
                    print(&quot;JSON 디코딩 실패&quot;)
                    completion(nil)
                    return
                }
                // guard let decodedData가 성공했을 경우
                completion(decodedData)
                //성공 범위 안에 들지 못한다면(200번대)
            } else {
                print(&quot;응답 오류&quot;)
                completion(nil)
            }
        }.resume() // resume해야 작동한다.
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alamofire를 이용해 서버데이터를 불러오는 메서드&lt;/p&gt;
&lt;pre id=&quot;code_1721022946497&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //Alamofire를 이용해 서버 데이터를 불러오는 메서드
    private func fetchDataByAlamofire&amp;lt;T: Decodable&amp;gt;(url: URL, completion: @escaping (Result&amp;lt;T, AFError&amp;gt;) -&amp;gt; Void) {
        AF.request(url).responseDecodable(of: T.self) { response in
            completion(response.result)
        }
    }&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;Alamofire이 설치가 되어 있지 않다면?&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alamofire를 위한 서버데이터 불러오기&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDNUM/btsIyJWkvww/Chokjxv4knAnXcNrNS5qR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDNUM/btsIyJWkvww/Chokjxv4knAnXcNrNS5qR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDNUM/btsIyJWkvww/Chokjxv4knAnXcNrNS5qR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDNUM%2FbtsIyJWkvww%2FChokjxv4knAnXcNrNS5qR1%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;3024&quot; height=&quot;1892&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크에 Alamofire를 불러온다.&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv7gdJ/btsIAVOwOmL/ynX07kneJ8xZYJTh5H0ExK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv7gdJ/btsIAVOwOmL/ynX07kneJ8xZYJTh5H0ExK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv7gdJ/btsIAVOwOmL/ynX07kneJ8xZYJTh5H0ExK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv7gdJ%2FbtsIAVOwOmL%2FynX07kneJ8xZYJTh5H0ExK%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;3024&quot; height=&quot;1892&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색했을 때 안 나오면,&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bySjAr/btsIAXS6CA6/sgF4l8Glsaoi1Kvn8kPIrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bySjAr/btsIAXS6CA6/sgF4l8Glsaoi1Kvn8kPIrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bySjAr/btsIAXS6CA6/sgF4l8Glsaoi1Kvn8kPIrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbySjAr%2FbtsIAXS6CA6%2FsgF4l8Glsaoi1Kvn8kPIrk%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;3024&quot; height=&quot;1244&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브에서 url을 따서 검색해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Alamofire url :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/Alamofire/Alamofire&quot;&gt;https://github.com/Alamofire/Alamofire&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJZpaT/btsIzcKMsoe/whE0opVzagnxPu6w8mK9ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJZpaT/btsIzcKMsoe/whE0opVzagnxPu6w8mK9ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJZpaT/btsIzcKMsoe/whE0opVzagnxPu6w8mK9ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJZpaT%2FbtsIzcKMsoe%2FwhE0opVzagnxPu6w8mK9ak%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;3024&quot; height=&quot;1244&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/urP5w/btsIzVogUzv/PbsYIZHcfKMKAJzSa5vNP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/urP5w/btsIzVogUzv/PbsYIZHcfKMKAJzSa5vNP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/urP5w/btsIzVogUzv/PbsYIZHcfKMKAJzSa5vNP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FurP5w%2FbtsIzVogUzv%2FPbsYIZHcfKMKAJzSa5vNP0%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;3024&quot; height=&quot;1244&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euy0F2/btsIyDPDqcO/5qUwrLFBKtQo8dW2DBJKd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euy0F2/btsIyDPDqcO/5qUwrLFBKtQo8dW2DBJKd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euy0F2/btsIyDPDqcO/5qUwrLFBKtQo8dW2DBJKd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feuy0F2%2FbtsIyDPDqcO%2F5qUwrLFBKtQo8dW2DBJKd0%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;3024&quot; height=&quot;1244&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측과 중앙에서 Alamofire 프레임워크가 잘 다운로드 받아진 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6V7RN/btsIz88F6KH/ki7sdBgtCNPdahEnTk9In0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6V7RN/btsIz88F6KH/ki7sdBgtCNPdahEnTk9In0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6V7RN/btsIz88F6KH/ki7sdBgtCNPdahEnTk9In0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6V7RN%2FbtsIz88F6KH%2Fki7sdBgtCNPdahEnTk9In0%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;3024&quot; height=&quot;1322&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController에 import Alamofire를 해주고, 위 코드를 작성한다.&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;*** Snapkit 다운로드 받을 때도 문제였는데 Daynamic까지 깔았을 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;No such file or directory: '/Users/sweetyejin/Library/Developer/Xcode/DerivedData/weatherAppPractice-ewpajbpjzttwjthftdcfwnvoswst/Build/Products/Debug-iphonesimulator/PackageFrameworks/AlamofireDynamic.framework/AlamofireDynamic' 이런류의 오류가 뜨면 프레임워크에서 Dynamic만 삭제해주면 오류 없이 실행 된다. 다운로드 받는 방법이 여러가지가 있는데 위와 같은 방법으로 할 때 적용되는&amp;nbsp; 방법이다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt;private&lt;/b&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;b&gt;func&lt;/b&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;fetchCurrentWeatherData&lt;span&gt;()에서 fetchData를 받아올 때 Alamofire로 서버를 불러올 수 있도록 다음과 같이 코드를 써볼 수 있다.&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;&lt;span&gt;기존&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1721023777316&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; //서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
       var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 url&quot;)
            return
        }
        fetchData(url: url) { [weak self] (result: CurrentWeatherResult?) in
            guard let self, let result else { return }
            
            // 메인스레드에서 작동하도록 하는 메서드
            DispatchQueue.main.async {
                self.tempLabel.text = &quot;\(Int(result.main.temp))&amp;deg;C&quot; // 서버에서 가져온 현재 날씨 데이터가 불러와짐
                self.tempMinLabel.text = &quot;최저: \(Int(result.main.tempMin))&amp;deg;C&quot;
                self.tempMaxLabel.text = &quot;최고: \(Int(result.main.tempMax))&amp;deg;C&quot;
            }
            guard let imageUrl = URL(string: &quot;https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png&quot;) else {
                return
            }
            
            // image를 로드하는 작업은 백그라운드 쓰레드 작업
            if let data = try? Data(contentsOf: imageUrl) {
                if let image = UIImage(data: data) {
                    DispatchQueue.main.async {
                        self.imageView.image = image
                    }
                }
            }
        }
    }&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;alamofire import 후&lt;/p&gt;
&lt;pre id=&quot;code_1721024637096&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //서버에서 현재 날씨 데이터를 불러오는 메서드
    private func fetchCurrentWeatherData() {
       var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/weather&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 url&quot;)
            return
        }
        
        fetchDataByAlamofire(url: url) { [weak self] (result: Result&amp;lt;CurrentWeatherResult, AFError&amp;gt;) in
            guard let self else { return }
            switch result {
            case .success(let result):
                DispatchQueue.main.async {
                    self.tempLabel.text = &quot;\(Int(result.main.temp))&amp;deg;C&quot;
                    self.tempMinLabel.text = &quot;최저: \(Int(result.main.tempMin))&amp;deg;C&quot;
                    self.tempMaxLabel.text = &quot;최고: \(Int(result.main.tempMax))&amp;deg;C&quot;
                }
                
                guard let imageUrl = URL(string: &quot;https://openweathermap.org/img/wn/\(result.weather[0].icon)@2x.png&quot;) else {
                    return
                }
                // AlamoFire를 사용한 이미지 로드
                AF.request(imageUrl).responseData { response in
                    if let data = response.data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            self.imageView.image = image
                        }
                    }
                }
                
            case .failure(let error):
                print(&quot;데이터 로드 실패: \(error)&quot;)
            }
            
        }
 
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;private&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;func&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;fetchForecastWeatherData()에서 fetchData를 받아올 때 Alamofire로 서버를 불러올 수 있도록 다음과 같이 코드를 써볼 수 있다.&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;AlamoFire 적용 전&lt;/p&gt;
&lt;pre id=&quot;code_1721024733410&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 서버에서 5일 간 날씨 데이터를 불러오는 메서드
    private func fetchForecastData() {
        var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/forecast&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 URL&quot;)
            return
        }
        fetchData(url: url) { [weak self] (result: ForecastWeatherResult?) in
            guard let self, let result else { return }
            
            // 콘솔에다가 데이터 잘 불러왔는지 찍어보기
            for forecastWeather in result.list {
                print(&quot;\(forecastWeather.main)\n\(forecastWeather.dtTxt)\n\n&quot;)
            }
            DispatchQueue.main.async {
                self.dataSource = result.list
                self.tableView.reloadData() // 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;적용 후&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1721025119862&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    // 서버에서 5일 간 날씨 데이터를 불러오는 메서드
    private func fetchForecastData() {
        var urlComponents = URLComponents(string: &quot;https://api.openweathermap.org/data/2.5/forecast&quot;)
        urlComponents?.queryItems = self.urlQueryItems
        
        guard let url = urlComponents?.url else {
            print(&quot;잘못된 URL&quot;)
            return
        }
        
        fetchDataByAlamofire(url: url) { [weak self] (result: Result&amp;lt;ForecastWeatherResult, AFError&amp;gt;) in
            
            guard let self else { return }
            switch result {
            case .success(let result):
                DispatchQueue.main.async {
                    self.dataSource = result.list
                    self.tableView.reloadData()
                }
                
            case .failure(let error):
                print(&quot;데이터 로드 실패: \(error)&quot;)
            }
            
        }
   }&lt;/code&gt;&lt;/pre&gt;</description>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/103</guid>
      <comments>https://bluewiper.tistory.com/103#entry103comment</comments>
      <pubDate>Mon, 15 Jul 2024 14:57:28 +0900</pubDate>
    </item>
    <item>
      <title>날씨앱만들기 (2) UI 구성 1</title>
      <link>https://bluewiper.tistory.com/101</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ow7Hx/btsIwOoB4Tp/TtfwDoKIPMUvC3nEjVYXe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ow7Hx/btsIwOoB4Tp/TtfwDoKIPMUvC3nEjVYXe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ow7Hx/btsIwOoB4Tp/TtfwDoKIPMUvC3nEjVYXe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOw7Hx%2FbtsIwOoB4Tp%2FTtfwDoKIPMUvC3nEjVYXe1%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;185&quot; height=&quot;381&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;1888&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;일단은 UI 구성이 잘 되었는지 확인이 필요하니까 임의의 컬러, 텍스트값을 넣어서 UI 구성을 해준다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1720691382994&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import SnapKit

class ViewController: UIViewController {

    //현재 위치한 도시를 알려주는 라벨
    private let titleLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;서울특별시&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 30)
        return label
    }()
    
    //기온을 나타내는 라벨
    private let tempLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;20도&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 50)
        return label
    }()
    
    //최저기온을 나타내는 라벨
    private let tempMinLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;20도&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 20)
        return label
    }()
    
    //최고기온 나타내는 라벨
    private let tempMaxLabel: UILabel = {
        let label = UILabel()
        label.text = &quot;20도&quot;
        label.textColor = .white
        label.font = .boldSystemFont(ofSize: 20)
        return label
    }()
    
    //최저기온과 최고기온을 담는 Horizontal Stack View
    private let tempStackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .horizontal
        stackView.spacing = 20
        stackView.distribution = .fillEqually
        return stackView
    }()
    
    //날씨이미지를 담은 이미지뷰
    private let imageView: UIImageView = {
        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.backgroundColor = .gray
        return imageView
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureUI()

    }

    private func configureUI() {
        view.backgroundColor = .black
        
        //각 프로퍼티 뷰 생성
        [
            titleLabel,
            tempLabel,
            tempStackView,
            imageView
        ].forEach { view.addSubview($0) }
        
        //tempStackView에 들어갈 최저기온과 최고기온 뷰 추가
        [
            tempMinLabel,
            tempMaxLabel
        ].forEach { tempStackView.addArrangedSubview($0) }
            
        //각 프로퍼티 제약 조건
        titleLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalToSuperview().offset(120)
        }
        
        tempLabel.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(titleLabel.snp.bottom).offset(10)
        }
        
        tempStackView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.top.equalTo(tempLabel.snp.bottom).offset(10)
        }
        
        imageView.snp.makeConstraints {
            $0.centerX.equalToSuperview()
            $0.width.height.equalTo(160)
            $0.top.equalTo(tempStackView.snp.bottom).offset(20)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 구성한 뒤에 서버에서 불러온 데이터로 현재 위치, 현재 기온, 최저 및 최고 기온에 대해 적용해줄 수 있는 로직을 짜야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래가 제네릭을 사용해서 url을 가져오는 가장 일반적인 메서드라고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 하나의 url만 쓰는게 아니니까 하나의 타입을 지정하는 게 아니고, 재사용을 위해 일반적인 메서드를 쓰는 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1720693137495&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서버 데이터를 불러오는 메서드
    private func fetchData&amp;lt;T: Decodable&amp;gt;(url: URL, completion: @escaping (T?) -&amp;gt; Void) {
        let session = URLSession(configuration: .default)
        session.dataTask(with: URLRequest(url: url)) { data, response, error in
            guard let data, error == nil else {
                print(&quot;데이터 로드 실패&quot;)
                completion(nil)
                return
            }
            // http status code 성공 범위는 200번대
            let successRange = 200..&amp;lt;300
            //HTTPURLResponse 안에 http status code를 깔기 위해 타입 캐스팅을 해준다.
            if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) {
                // 받은 데이터가 JSON 형태일테니 Swift 형태로 디코딩해준다.
                guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else {
                    print(&quot;JSON 디코딩 실패&quot;)
                    completion(nil)
                    return
                }
                // guard let decodedData가 성공했을 경우
                completion(decodedData)
                //성공 범위 안에 들지 못한다면(200번대)
            } else {
                print(&quot;응답 오류&quot;)
                completion(nil)
            }
        }.resume() // resume해야 작동한다.
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제네릭과 이스케이핑..들어본 것 같은데 ㅠㅠ&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720693189656&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private func fetchData&amp;lt;T: Decodable&amp;gt;(url: URL, completion: @escaping (T?) -&amp;gt; Void) {
        let session = URLSession(configuration: .default)
        session.dataTask(with: URLRequest(url: url)) { data, response, error in
            guard let data, error == nil else {
                print(&quot;데이터 로드 실패&quot;)
                completion(nil)
                return&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;T&amp;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;lt; T: decodable&amp;gt; ~ (T?) 이 줄에서 (T?)에는 어떤 타입도 들어올 수 있긴하나, 앞쪽에 어떤 T 타입이냐를 물었을 때 Decodable이라고 적어줬기 때문에 Decodable 타입만 준수한다면 어떤 타입이든 들어올 수 있다. 예를 들어 Int, Double, Codable을 채택하고 있는 구조체 등도 가능하다.&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;@escaping 클로저?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;@escaping (T?) -&amp;gt; Void&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통의 코드는 함수 안에서 작성 됐을 때 함수가 끝이나면 함께 책임을 다하고 소멸한다. 하지만 escaping으로 선언한 클로저는&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;&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;</description>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/101</guid>
      <comments>https://bluewiper.tistory.com/101#entry101comment</comments>
      <pubDate>Thu, 11 Jul 2024 18:50:29 +0900</pubDate>
    </item>
    <item>
      <title>날씨앱 만들기 (1) 초기 세팅</title>
      <link>https://bluewiper.tistory.com/100</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z6q8g/btsIwV8zg1q/oKzkkvXWnvpyqPMiBKrdpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z6q8g/btsIwV8zg1q/oKzkkvXWnvpyqPMiBKrdpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z6q8g/btsIwV8zg1q/oKzkkvXWnvpyqPMiBKrdpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ6q8g%2FbtsIwV8zg1q%2FoKzkkvXWnvpyqPMiBKrdpK%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;3024&quot; height=&quot;704&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 만들어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드베이스로 할 것이기 때문에 main은 지워준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;저번에 코드베이스 main 지울 때랑 이번 강의에서 알려주는 방법이 다른 것 같다..? 그래서 일단은 다 기록으로 남기려고 한다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;1. 파일메인을 지우고 나면, info에 가서 Application Scene Manifest를 날려준다.&amp;nbsp;&lt;/span&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/Kb7mF/btsIvQN11Gh/fPNVsBpDV6kp7PEKBnccO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kb7mF/btsIvQN11Gh/fPNVsBpDV6kp7PEKBnccO0/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kb7mF/btsIvQN11Gh/fPNVsBpDV6kp7PEKBnccO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKb7mF%2FbtsIvQN11Gh%2FfPNVsBpDV6kp7PEKBnccO0%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;3024&quot; height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/doDVMc/btsIwu4Ii7Y/MeBt4ehFzwX11DI1MnxST1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/doDVMc/btsIwu4Ii7Y/MeBt4ehFzwX11DI1MnxST1/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/doDVMc/btsIwu4Ii7Y/MeBt4ehFzwX11DI1MnxST1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdoDVMc%2FbtsIwu4Ii7Y%2FMeBt4ehFzwX11DI1MnxST1%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;3024&quot; height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌 : 날리기 전, 우 : 날린 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SceneDelegate도 날려준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZsUww/btsIwylJIxm/mMuKTn6dbDa9sMAUv5xNk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZsUww/btsIwylJIxm/mMuKTn6dbDa9sMAUv5xNk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZsUww/btsIwylJIxm/mMuKTn6dbDa9sMAUv5xNk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZsUww%2FbtsIwylJIxm%2FmMuKTn6dbDa9sMAUv5xNk0%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;3024&quot; height=&quot;704&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;704&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;3. AppDelegate에서 관련 메서드 지운다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WJ9Hr/btsIu1pfvOY/cFgskQJB6SD8i6sgKwJfxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WJ9Hr/btsIu1pfvOY/cFgskQJB6SD8i6sgKwJfxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WJ9Hr/btsIu1pfvOY/cFgskQJB6SD8i6sgKwJfxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWJ9Hr%2FbtsIu1pfvOY%2FcFgskQJB6SD8i6sgKwJfxk%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;3024&quot; height=&quot;1462&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1462&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;3. 프로젝트 파일의 Build Setting에서 main을 필터링해서 삭제한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b39dDR/btsIvcdbPh8/BT3KjdokTjHTEVsWxTxLdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b39dDR/btsIvcdbPh8/BT3KjdokTjHTEVsWxTxLdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b39dDR/btsIvcdbPh8/BT3KjdokTjHTEVsWxTxLdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb39dDR%2FbtsIvcdbPh8%2FBT3KjdokTjHTEVsWxTxLdK%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;3024&quot; height=&quot;702&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;702&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;4. AppDelegate에 가서 window 생성&lt;/p&gt;
&lt;pre id=&quot;code_1720689167769&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UINavigationController(rootViewController: ViewController())
        window.makeKeyAndVisible()
        self.window = window
        
        return true
    }
}&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;5. ViewController 가서 초기 세팅 잘 되었는지 브레이크 포인트 걸어놓고 실행해보기&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkng29/btsIu3t2jPf/JwA4seBBWGNdwKJJA8vfSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkng29/btsIu3t2jPf/JwA4seBBWGNdwKJJA8vfSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkng29/btsIu3t2jPf/JwA4seBBWGNdwKJJA8vfSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkng29%2FbtsIu3t2jPf%2FJwA4seBBWGNdwKJJA8vfSK%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;3024&quot; height=&quot;792&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기세팅 완료!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/100</guid>
      <comments>https://bluewiper.tistory.com/100#entry100comment</comments>
      <pubDate>Thu, 11 Jul 2024 18:15:29 +0900</pubDate>
    </item>
    <item>
      <title>날씨앱 만들기 open weather API 가져오기</title>
      <link>https://bluewiper.tistory.com/99</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;OpenWeather - 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;API doc을 들어가면 API 명세를 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fh3lP/btsIvPatQmN/uTs9Eq0aLPoKMsZMAsT2D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fh3lP/btsIvPatQmN/uTs9Eq0aLPoKMsZMAsT2D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fh3lP/btsIvPatQmN/uTs9Eq0aLPoKMsZMAsT2D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFh3lP%2FbtsIvPatQmN%2FuTs9Eq0aLPoKMsZMAsT2D1%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;3024&quot; height=&quot;1486&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 작성에 앞서 내가 받게 될 데이터가 어떤 내용을 담고 있을지 알아보고 싶어졌다.&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;테스트로 Current weather data로 들어가보면,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d3NU6F/btsIwCBsmDL/XDBx2b2pwfW9Fn6L8MKB1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d3NU6F/btsIwCBsmDL/XDBx2b2pwfW9Fn6L8MKB1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d3NU6F/btsIwCBsmDL/XDBx2b2pwfW9Fn6L8MKB1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd3NU6F%2FbtsIwCBsmDL%2FXDBx2b2pwfW9Fn6L8MKB1k%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;3024&quot; height=&quot;1486&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1486&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;쭉 내려가보면 API call 예시가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPFnFi/btsIu2O9kYt/kVKiqUgYPqnCGhxzmqG2EK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPFnFi/btsIu2O9kYt/kVKiqUgYPqnCGhxzmqG2EK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPFnFi/btsIu2O9kYt/kVKiqUgYPqnCGhxzmqG2EK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPFnFi%2FbtsIu2O9kYt%2FkVKiqUgYPqnCGhxzmqG2EK%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;1238&quot; height=&quot;356&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 url을 복사하고,&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;&amp;nbsp;{API key}에는&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ9A1B/btsIu7XdJne/47K2scMXrjChRIO40iWuo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ9A1B/btsIu7XdJne/47K2scMXrjChRIO40iWuo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ9A1B/btsIu7XdJne/47K2scMXrjChRIO40iWuo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ9A1B%2FbtsIu7XdJne%2F47K2scMXrjChRIO40iWuo0%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;3000&quot; height=&quot;702&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;702&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;생성된 나의 API Key를 넣어준다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1464&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/URgJq/btsIvi5c6vk/xxyIKwCKMrMQlZoS6mhyY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/URgJq/btsIvi5c6vk/xxyIKwCKMrMQlZoS6mhyY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/URgJq/btsIvi5c6vk/xxyIKwCKMrMQlZoS6mhyY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FURgJq%2FbtsIvi5c6vk%2FxxyIKwCKMrMQlZoS6mhyY1%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;3024&quot; height=&quot;1464&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1464&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>
      <author>bluewiper</author>
      <guid isPermaLink="true">https://bluewiper.tistory.com/99</guid>
      <comments>https://bluewiper.tistory.com/99#entry99comment</comments>
      <pubDate>Thu, 11 Jul 2024 15:49:42 +0900</pubDate>
    </item>
  </channel>
</rss>