<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>lucete</title>
    <link>https://natatory.tistory.com/</link>
    <description>iOS개발을 공부합니다</description>
    <language>ko</language>
    <pubDate>Sun, 12 Apr 2026 22:56:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Lyla.</managingEditor>
    <item>
      <title>기획 문서 - 기획에서 필요한 문서를 알아보자</title>
      <link>https://natatory.tistory.com/632</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문서 목록]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-end=&quot;746&quot; data-start=&quot;516&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;516&quot;&gt;&lt;b&gt;기능 명세서(Feature List)&lt;/b&gt;: 페이지별 또는 기능별 세부 설명&lt;/li&gt;
&lt;li data-end=&quot;610&quot; data-start=&quot;564&quot;&gt;&lt;b&gt;요구사항 정의서&lt;/b&gt;: 비즈니스 요건, 시스템 요건, 비기능 요건 등 포함&lt;/li&gt;
&lt;li data-end=&quot;659&quot; data-start=&quot;611&quot;&gt;&lt;b&gt;유즈케이스(Use Case) 문서&lt;/b&gt;: 사용자 행동과 시스템 반응 중심 정리&lt;/li&gt;
&lt;li data-end=&quot;705&quot; data-start=&quot;660&quot;&gt;&lt;b&gt;와이어프레임&lt;/b&gt;: 화면 구성의 초기 설계안 (주로 기획자가 직접 작성)&lt;/li&gt;
&lt;li data-end=&quot;746&quot; data-start=&quot;706&quot;&gt;&lt;b&gt;스크린 플로우(Sitemap)&lt;/b&gt;: 전체 메뉴 구조와 화면 흐름도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 문서의 형태는 다르겠지만.. 나의 경우를 서술한다.&amp;nbsp; 프로젝트별로 위의 모든 문서를 작성해야 하는 것도 아니고, 위의 문서를 다 작성했다고 기획이 끝난 것도 아니다. 필요한 문서가 뭔지 의사소통을 통해서 알아내야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기능명세서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능명세서 - 엑셀로 작성, 대분류와 소분류로 나누어 필요한 기능을 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;요구사항 정의서&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 요구사항이 필요한지 엑셀로 작성한다. 서비스 형태(PC/Web), 모바일이라면 어떤 os 버전부터 서비스 예정인지? 등을 작성. (-&amp;gt; os 버전별로 지원되는 기능을 알고 있으면 좋다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;와이어 프레임&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MfEAv/btsOPo6jUAn/9FQJ6L0kQAxkNyK7HDKFV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MfEAv/btsOPo6jUAn/9FQJ6L0kQAxkNyK7HDKFV1/img.png&quot; data-alt=&quot;Miro&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MfEAv/btsOPo6jUAn/9FQJ6L0kQAxkNyK7HDKFV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMfEAv%2FbtsOPo6jUAn%2F9FQJ6L0kQAxkNyK7HDKFV1%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;539&quot; height=&quot;354&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Miro&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;스크린 플로우&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Oy5mC/btsOQcEs9p0/1GWTrinbBrUQTyvaJfvBM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Oy5mC/btsOQcEs9p0/1GWTrinbBrUQTyvaJfvBM0/img.png&quot; data-alt=&quot;figma&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Oy5mC/btsOQcEs9p0/1GWTrinbBrUQTyvaJfvBM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOy5mC%2FbtsOQcEs9p0%2F1GWTrinbBrUQTyvaJfvBM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;261&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;figma&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;화면 간에 이동 경로 표시, 앞서 그린 와이어 프레임을 피그마 플러그인 AutoFlow을 통해 흐름을 표시해줍니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>기록</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/632</guid>
      <comments>https://natatory.tistory.com/632#entry632comment</comments>
      <pubDate>Wed, 25 Jun 2025 13:51:19 +0900</pubDate>
    </item>
    <item>
      <title>천문학자는 별을 보지 않는다</title>
      <link>https://natatory.tistory.com/606</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;보이저는 창백한 푸른 점을 잠시 응시한 뒤, 다시 원래대로 기수를 돌렸다. 더 멀리, 통신도 닿지 않고 누구의 지령도 받지 않는 곳으로, 보이저느 수명이 다하는 날까지 전진할 것이다. 지구에서부터 가지고 간 연료는 바닥났다. 태양의 중력은 점차 가벼워지고, 그 빛조차도 너무 희미하다. 그래도 멈추지 않는다. 춥고 어둡고 광활한 우주로 묵묵히 나아간다. 그렇게 우리는 각자의 우주를 만들어간다. 그렇게 어른이 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>기록/독서</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/606</guid>
      <comments>https://natatory.tistory.com/606#entry606comment</comments>
      <pubDate>Wed, 27 Nov 2024 09:51:17 +0900</pubDate>
    </item>
    <item>
      <title>consumer = state</title>
      <link>https://natatory.tistory.com/554</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer&amp;lt;EventProvider&amp;gt;는&amp;nbsp;Flutter의&amp;nbsp;provider&amp;nbsp;패키지에서&amp;nbsp;제공하는&amp;nbsp;위젯&amp;nbsp;중&amp;nbsp;하나로,&amp;nbsp;상태를&amp;nbsp;소비하고&amp;nbsp;해당&amp;nbsp;상태에&amp;nbsp;따라&amp;nbsp;UI를&amp;nbsp;빌드하는&amp;nbsp;역할을&amp;nbsp;합니다.&amp;nbsp;주로&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;상황에서&amp;nbsp;사용됩니다:&lt;br /&gt;&lt;br /&gt;특정&amp;nbsp;상태를&amp;nbsp;읽고&amp;nbsp;UI에&amp;nbsp;반영:&amp;nbsp;Consumer&amp;lt;EventProvider&amp;gt;&amp;nbsp;위젯&amp;nbsp;내부에서&amp;nbsp;상태를&amp;nbsp;읽고,&amp;nbsp;이&amp;nbsp;상태에&amp;nbsp;따라&amp;nbsp;UI를&amp;nbsp;업데이트합니다.&amp;nbsp;예를&amp;nbsp;들어,&amp;nbsp;EventProvider&amp;nbsp;클래스가&amp;nbsp;상태를&amp;nbsp;관리하고&amp;nbsp;있다면,&amp;nbsp;Consumer&amp;lt;EventProvider&amp;gt;&amp;nbsp;내부에서&amp;nbsp;해당&amp;nbsp;상태를&amp;nbsp;읽어서&amp;nbsp;이벤트&amp;nbsp;목록을&amp;nbsp;보여줄&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;부분적인&amp;nbsp;UI&amp;nbsp;갱신:&amp;nbsp;Consumer는&amp;nbsp;자체적으로&amp;nbsp;갱신됩니다.&amp;nbsp;즉,&amp;nbsp;상태가&amp;nbsp;변경될&amp;nbsp;때&amp;nbsp;해당&amp;nbsp;Consumer&amp;nbsp;내부의&amp;nbsp;UI만&amp;nbsp;다시&amp;nbsp;렌더링됩니다.&amp;nbsp;이는&amp;nbsp;성능을&amp;nbsp;향상시키고,&amp;nbsp;불필요한&amp;nbsp;전체&amp;nbsp;UI의&amp;nbsp;리빌드를&amp;nbsp;방지하는&amp;nbsp;데&amp;nbsp;도움을&amp;nbsp;줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1718637229922&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expanded(
            child: Consumer&amp;lt;EventProvider&amp;gt;(
              builder: (context, eventProvider, child) {
                final events = eventProvider.getEventsForDay(_selectedDay);
                print('Events for selected day ($_selectedDay): $events');
                return ListView.builder(
                  itemCount: events.length,
                  itemBuilder: (context, index) {
                    final event = events[index];
                    return Draggable&amp;lt;Event&amp;gt;(
                      data: event,
                      feedback: Material(
                        child: Container(
                          padding: EdgeInsets.all(8.0),
                          color: Colors.blue,
                          child: Text(event.title, style: TextStyle(color: Colors.white)),
                        ),
                      ),
                      childWhenDragging: Opacity(
                        opacity: 0.5,
                        child: ListTile(
                          title: Text(event.title),
                        ),
                      ),
                      child: ListTile(
                        title: Text(event.title),
                        onTap: () async {
                          // 상세보기 및 수정
                          final result = await Navigator.of(context).push(MaterialPageRoute(
                            builder: (context) =&amp;gt; EventDetailPage(event: event),
                          ));
                          if (result == true) {
                            setState(() {}); // 캘린더 상태 업데이트
                          }
                        },
                        onLongPress: () {
                          // 이벤트 복사
                          eventProvider.addEvent(event.copyWith(
                            dateTime: event.dateTime.add(Duration(days: 1)),
                          ));
                          setState(() {}); // 캘린더 상태 업데이트
                        },
                      ),
                    );
                  },
                );
              },
            ),
          ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/554</guid>
      <comments>https://natatory.tistory.com/554#entry554comment</comments>
      <pubDate>Tue, 18 Jun 2024 00:14:44 +0900</pubDate>
    </item>
    <item>
      <title>gitlab - host, 호스팅 파일 수정 git 설정 오류</title>
      <link>https://natatory.tistory.com/530</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[에러메세지]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh:&amp;nbsp;Could&amp;nbsp;not&amp;nbsp;resolve&amp;nbsp;hostname&amp;nbsp;gitlab.local.intsoft.kr:&amp;nbsp;nodename&amp;nbsp;nor&amp;nbsp;servname&amp;nbsp;provided,&amp;nbsp;or&amp;nbsp;not&amp;nbsp;known&lt;br /&gt;fatal:&amp;nbsp;Could&amp;nbsp;not&amp;nbsp;read&amp;nbsp;from&amp;nbsp;remote&amp;nbsp;repository.&lt;br /&gt;&lt;br /&gt;Please&amp;nbsp;make&amp;nbsp;sure&amp;nbsp;you&amp;nbsp;have&amp;nbsp;the&amp;nbsp;correct&amp;nbsp;access&amp;nbsp;rights&lt;br /&gt;and&amp;nbsp;the&amp;nbsp;repository&amp;nbsp;exists.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. ping 찍어보기&lt;/h4&gt;
&lt;pre id=&quot;code_1713162222801&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ping [사이트 url]&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;ping 이 안찍힘&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; url 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;로컬 시스템의 호스트 파일을 수정할 수 있습니다. 호스트 파일을 수정하면 특정 호스트 이름을 IP 주소로 해석하도록 지시할 수 있음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;2. 호스트 파일 열기&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713162322849&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo nano /etc/hosts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 호스트 파일 수정하기&lt;/h4&gt;
&lt;pre id=&quot;code_1713162378827&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;10.10.10.XXX [연결할 url]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트&amp;nbsp;파일의&amp;nbsp;끝에&amp;nbsp;다음과&amp;nbsp;같은&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;편집이&amp;nbsp;완료되면&amp;nbsp;Ctrl&amp;nbsp;+&amp;nbsp;X를&amp;nbsp;눌러&amp;nbsp;나가기를&amp;nbsp;선택하고&amp;nbsp;Y를&amp;nbsp;눌러&amp;nbsp;변경&amp;nbsp;사항을&amp;nbsp;저장합니다.&lt;br /&gt;&lt;br /&gt;저장한&amp;nbsp;후에는&amp;nbsp;Enter를&amp;nbsp;눌러&amp;nbsp;편집기를&amp;nbsp;종료합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. ping 다시 찍기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 제대로 찍힌다. 연결 완료&amp;nbsp;&lt;/p&gt;</description>
      <category>Front-end</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/530</guid>
      <comments>https://natatory.tistory.com/530#entry530comment</comments>
      <pubDate>Mon, 15 Apr 2024 15:28:23 +0900</pubDate>
    </item>
    <item>
      <title>마커에 infoView표시하기</title>
      <link>https://natatory.tistory.com/485</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n91IH/btsFpRCmaxu/XglFq861B0rDwyHspSY2wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n91IH/btsFpRCmaxu/XglFq861B0rDwyHspSY2wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n91IH/btsFpRCmaxu/XglFq861B0rDwyHspSY2wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn91IH%2FbtsFpRCmaxu%2FXglFq861B0rDwyHspSY2wk%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;843&quot; height=&quot;301&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;843&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1709302727556&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function createMarker(labelContent, lat, lng) {
            var markerOptions = {
                position: new naver.maps.LatLng(lat, lng),
                map: map
            };

            var marker = new naver.maps.Marker(markerOptions);

            var contentString = [
                '&amp;lt;div class=&quot;iw_inner&quot;&amp;gt;',
                '   &amp;lt;h3&amp;gt;마커 정보&amp;lt;/h3&amp;gt;',
                '   &amp;lt;p&amp;gt;마커 내용: ' + labelContent + '&amp;lt;br&amp;gt;',
                '       좌표: ' + lat + ', ' + lng + '&amp;lt;/p&amp;gt;',
                '&amp;lt;/div&amp;gt;'
            ].join('');

            var infowindow = new naver.maps.InfoWindow({
                content: contentString
            });

            naver.maps.Event.addListener(marker, 'click', function (e) {
                if (infowindow.getMap()) {
                    infowindow.close();
                } else {
                    infowindow.open(map, marker);
                }
            });
        }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front-end/HTML</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/485</guid>
      <comments>https://natatory.tistory.com/485#entry485comment</comments>
      <pubDate>Fri, 1 Mar 2024 23:19:04 +0900</pubDate>
    </item>
    <item>
      <title>NavigationStack에서 title Zstack사용으로 여백이 생김</title>
      <link>https://natatory.tistory.com/478</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tol4k/btsFdmWUDTG/5kXJP8WebweIMlfGPzBKGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tol4k/btsFdmWUDTG/5kXJP8WebweIMlfGPzBKGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tol4k/btsFdmWUDTG/5kXJP8WebweIMlfGPzBKGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftol4k%2FbtsFdmWUDTG%2F5kXJP8WebweIMlfGPzBKGk%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;335&quot; height=&quot;686&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;686&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;floatingButton 때문에 ZStack은 써야하는 상황&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List를 띄우니 여백이 많이 남는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708605243576&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    @State var diaryStore:DiaryStore = DiaryStore()
    
    var body: some View {
        NavigationStack{
            ZStack  {
                ListView(diaryStore: $diaryStore)
                VStack {
                    Spacer()
                    HStack {
                        Spacer()
                        Button(action: {
                            print(&quot;플로팅 버튼이 클릭되었습니다.&quot;)
                        }) {
                            Image(systemName: &quot;plus&quot;)
                                .font(.title)
                                .padding()
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .clipShape(Circle())
                        }
                    }
                }
            }
            .navigationTitle(&quot;MoodDiary&quot;)
        }
        .padding()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;listStyle을 inset으로 설정하기&lt;/h3&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/b967IZ/btsFeGUJ4x7/QFsMSdwu8UhEpLKBIIukI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b967IZ/btsFeGUJ4x7/QFsMSdwu8UhEpLKBIIukI0/img.png&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;686&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/b967IZ/btsFeGUJ4x7/QFsMSdwu8UhEpLKBIIukI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb967IZ%2FbtsFeGUJ4x7%2FQFsMSdwu8UhEpLKBIIukI0%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;335&quot; height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2hNbB/btsFftU3A3R/NjgEM250AldKcDiQB6HPdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2hNbB/btsFftU3A3R/NjgEM250AldKcDiQB6HPdK/img.png&quot; data-origin-width=&quot;335&quot; data-origin-height=&quot;686&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/2hNbB/btsFftU3A3R/NjgEM250AldKcDiQB6HPdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2hNbB%2FbtsFftU3A3R%2FNjgEM250AldKcDiQB6HPdK%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;335&quot; height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708605605618&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ListView(diaryStore: $diaryStore)
                    .listStyle(.inset)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그나마 봐줄만한데 Scroll을 올렸을 때 오른쪽처럼 안예쁘다..! '-'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift/SwiftUI</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/478</guid>
      <comments>https://natatory.tistory.com/478#entry478comment</comments>
      <pubDate>Thu, 22 Feb 2024 21:46:07 +0900</pubDate>
    </item>
    <item>
      <title>[SwiftUI] 플로팅 버튼 만들기</title>
      <link>https://natatory.tistory.com/477</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 주로 쓰이는 UI인 플로팅 버튼을 만들어봅시다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EVrsP/btsFaMnZW8X/MCqNUH5HpPOIMkFPkfdwHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EVrsP/btsFaMnZW8X/MCqNUH5HpPOIMkFPkfdwHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EVrsP/btsFaMnZW8X/MCqNUH5HpPOIMkFPkfdwHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEVrsP%2FbtsFaMnZW8X%2FMCqNUH5HpPOIMkFPkfdwHK%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;320&quot; height=&quot;664&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[구조 파악]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Zstack으로 ScrollView를 아래에, floatingButton을 위에 띄워준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. spacer를 두개 줘서 왼쪽 아래에 floatingButton을 위치시킨다. (Hstack, Vstack 이용)&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;pre id=&quot;code_1708569592173&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct ContentView: View {
    @State private var isButtonVisible = false
    
    let pages = [
       &quot;page1&quot;,&quot;page2&quot;,&quot;page3&quot;
    ]
    
    var body: some View {
        ZStack {
            ScrollView{
                VStack {
                    ForEach(pages,id: \.self) { image in
                        Image(image)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                    }
                }
            }
            
            if isButtonVisible {
                VStack {
                    Spacer()
                    HStack {
                        Spacer()
                        Button(action: {
                            print(&quot;플로팅 버튼이 클릭되었습니다.&quot;)
                        }) {
                            Image(systemName: &quot;plus&quot;)
                                .font(.title)
                                .padding()
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .clipShape(Circle())
                        }
                        .padding(.trailing, 16)
                        .padding(.bottom, 16)
                    }
                }
                
            }
        }
        .onAppear {
            isButtonVisible = 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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Swift/SwiftUI</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/477</guid>
      <comments>https://natatory.tistory.com/477#entry477comment</comments>
      <pubDate>Thu, 22 Feb 2024 11:43:52 +0900</pubDate>
    </item>
    <item>
      <title>MVVM / MVC</title>
      <link>https://natatory.tistory.com/476</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;M/V/C - Model / View / ViewController&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M/V/VM - Model / View / ViewModel&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;MVC에 ViewController의 책임이 과해져서 이를 ViewModel에 덜어준다.&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;한마디로, Data를 가지고 있는 instance/로직을 ViewModel에 전가함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVVM의 View는 그냥 보여주기만 하는 친구임&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 58px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.4729%; height: 20px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 41.0077%; height: 20px; text-align: center;&quot;&gt;MVVM의 View&lt;/td&gt;
&lt;td style=&quot;width: 47.5193%; height: 20px; text-align: center;&quot;&gt;MVC의 ViewController&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.4729%; height: 20px;&quot;&gt;있는 것&lt;/td&gt;
&lt;td style=&quot;width: 41.0077%; height: 20px;&quot;&gt;ViewModel&lt;/td&gt;
&lt;td style=&quot;width: 47.5193%; height: 20px;&quot;&gt;Data를 가지고 있는 instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 11.4729%; height: 18px;&quot;&gt;없는 것&lt;/td&gt;
&lt;td style=&quot;width: 41.0077%; height: 18px;&quot;&gt;Data를 가지고 있는 instance&lt;br /&gt;(이를 ViewModel로 보냄)&lt;/td&gt;
&lt;td style=&quot;width: 47.5193%; height: 18px;&quot;&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;h3 data-ke-size=&quot;size23&quot;&gt;의존성 기반으로도 참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVC&amp;nbsp;패턴은&amp;nbsp;주로&amp;nbsp;전통적인&amp;nbsp;GUI&amp;nbsp;기반&amp;nbsp;애플리케이션에서&amp;nbsp;사용되며,&amp;nbsp;애플리케이션을&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;주요&amp;nbsp;구성&amp;nbsp;요소로&amp;nbsp;분리합니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;-&amp;nbsp;Model&amp;nbsp;(모델):&amp;nbsp;데이터와&amp;nbsp;비즈니스&amp;nbsp;로직을&amp;nbsp;처리합니다.&amp;nbsp;데이터의&amp;nbsp;상태를&amp;nbsp;유지하고&amp;nbsp;조작하는&amp;nbsp;역할을&amp;nbsp;합니다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;View&amp;nbsp;(뷰):&amp;nbsp;사용자&amp;nbsp;인터페이스를&amp;nbsp;표시하고&amp;nbsp;사용자의&amp;nbsp;입력을&amp;nbsp;처리합니다.&amp;nbsp;사용자에게&amp;nbsp;정보를&amp;nbsp;표시하고&amp;nbsp;입력을&amp;nbsp;Controller에&amp;nbsp;전달합니다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;Controller&amp;nbsp;(컨트롤러):&amp;nbsp;사용자&amp;nbsp;입력을&amp;nbsp;처리하고&amp;nbsp;Model과&amp;nbsp;View&amp;nbsp;사이의&amp;nbsp;상호&amp;nbsp;작용을&amp;nbsp;조정합니다.&amp;nbsp;사용자&amp;nbsp;입력을&amp;nbsp;처리하여&amp;nbsp;Model의&amp;nbsp;상태를&amp;nbsp;변경하고,&amp;nbsp;변경된&amp;nbsp;데이터를&amp;nbsp;View에&amp;nbsp;반영합니다.&lt;br /&gt;&lt;br /&gt;MVC&amp;nbsp;패턴은&amp;nbsp;단방향&amp;nbsp;데이터&amp;nbsp;흐름을&amp;nbsp;가지며,&amp;nbsp;Model과&amp;nbsp;View는&amp;nbsp;직접적인&amp;nbsp;의존성이&amp;nbsp;없습니다.&amp;nbsp;대신,&amp;nbsp;Controller가&amp;nbsp;Model과&amp;nbsp;View를&amp;nbsp;조정하여&amp;nbsp;상호&amp;nbsp;작용합니다.&lt;br /&gt;&lt;br /&gt;반면에&amp;nbsp;MVVM&amp;nbsp;패턴은&amp;nbsp;주로&amp;nbsp;UI&amp;nbsp;중심&amp;nbsp;애플리케이션에서&amp;nbsp;사용되며,&amp;nbsp;애플리케이션을&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;주요&amp;nbsp;구성&amp;nbsp;요소로&amp;nbsp;분리합니다:&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;Model&amp;nbsp;(모델):&amp;nbsp;데이터와&amp;nbsp;비즈니스&amp;nbsp;로직을&amp;nbsp;처리합니다.&amp;nbsp;데이터의&amp;nbsp;상태를&amp;nbsp;유지하고&amp;nbsp;조작하는&amp;nbsp;역할을&amp;nbsp;합니다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;View&amp;nbsp;(뷰):&amp;nbsp;사용자&amp;nbsp;인터페이스를&amp;nbsp;표시하고,&amp;nbsp;ViewModel과&amp;nbsp;데이터&amp;nbsp;바인딩을&amp;nbsp;통해&amp;nbsp;데이터의&amp;nbsp;상태를&amp;nbsp;표시합니다.&amp;nbsp;사용자의&amp;nbsp;입력은&amp;nbsp;ViewModel로&amp;nbsp;전달됩니다.&lt;br /&gt;&lt;br /&gt;-&amp;nbsp;ViewModel&amp;nbsp;(뷰모델):&amp;nbsp;View와&amp;nbsp;Model&amp;nbsp;사이의&amp;nbsp;인터페이스&amp;nbsp;역할을&amp;nbsp;합니다.&amp;nbsp;View에&amp;nbsp;표시할&amp;nbsp;데이터를&amp;nbsp;제공하고,&amp;nbsp;사용자&amp;nbsp;입력을&amp;nbsp;처리하여&amp;nbsp;Model의&amp;nbsp;상태를&amp;nbsp;변경합니다.&amp;nbsp;또한,&amp;nbsp;View와의&amp;nbsp;데이터&amp;nbsp;바인딩을&amp;nbsp;통해&amp;nbsp;View의&amp;nbsp;상태를&amp;nbsp;업데이트합니다.&lt;br /&gt;&lt;br /&gt;MVVM&amp;nbsp;패턴은&amp;nbsp;양방향&amp;nbsp;데이터&amp;nbsp;바인딩을&amp;nbsp;사용하여&amp;nbsp;View와&amp;nbsp;ViewModel&amp;nbsp;사이의&amp;nbsp;상호&amp;nbsp;작용을&amp;nbsp;강화합니다.&amp;nbsp;ViewModel은&amp;nbsp;View에&amp;nbsp;대한&amp;nbsp;의존성이&amp;nbsp;없으며,&amp;nbsp;View는&amp;nbsp;ViewModel에&amp;nbsp;대한&amp;nbsp;의존성이&amp;nbsp;있습니다.&amp;nbsp;이를&amp;nbsp;통해&amp;nbsp;UI&amp;nbsp;로직과&amp;nbsp;비즈니스&amp;nbsp;로직을&amp;nbsp;분리하여&amp;nbsp;테스트&amp;nbsp;용이성과&amp;nbsp;재사용성을&amp;nbsp;향상시킵니다.&lt;br /&gt;&lt;br /&gt;MVC와&amp;nbsp;MVVM은&amp;nbsp;각각&amp;nbsp;다른&amp;nbsp;상황에&amp;nbsp;적합한&amp;nbsp;패턴이며,&amp;nbsp;애플리케이션의&amp;nbsp;크기,&amp;nbsp;복잡성&amp;nbsp;및&amp;nbsp;요구&amp;nbsp;사항에&amp;nbsp;따라&amp;nbsp;선택할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;</description>
      <category>Swift</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/476</guid>
      <comments>https://natatory.tistory.com/476#entry476comment</comments>
      <pubDate>Wed, 21 Feb 2024 22:11:07 +0900</pubDate>
    </item>
    <item>
      <title>Combine - 연산자 receive/sink/store</title>
      <link>https://natatory.tistory.com/475</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PucJv/btsE91S9JSN/kh7nPtzKTxLhi5L0E8WGr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PucJv/btsE91S9JSN/kh7nPtzKTxLhi5L0E8WGr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PucJv/btsE91S9JSN/kh7nPtzKTxLhi5L0E8WGr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPucJv%2FbtsE91S9JSN%2Fkh7nPtzKTxLhi5L0E8WGr1%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;285&quot; height=&quot;178&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1708519336434&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;viewModel.selectedItem
            .compactMap { $0 }
            .receive(on: RunLoop.main)
            .sink { framework in
                let sb = UIStoryboard(name: &quot;Detail&quot;, bundle: nil)
                let vc = sb.instantiateViewController(withIdentifier: &quot;FrameworkDetailViewController&quot;) as! FrameworkDetailViewController
                vc.viewModel = FrameworkDetailViewModel(framework: framework)
                self.present(vc, animated: true)
            }.store(in: &amp;amp;subscriptions)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당&amp;nbsp;코드는&amp;nbsp;Combine&amp;nbsp;프레임워크를&amp;nbsp;사용하여&amp;nbsp;데이터&amp;nbsp;흐름을&amp;nbsp;처리하는&amp;nbsp;방식을&amp;nbsp;보여주는&amp;nbsp;예시입니다.&amp;nbsp;코드를&amp;nbsp;각&amp;nbsp;부분별로&amp;nbsp;설명하겠습니다:&lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;`viewModel.selectedItem`:&amp;nbsp;`viewModel`&amp;nbsp;객체의&amp;nbsp;`selectedItem`&amp;nbsp;속성을&amp;nbsp;나타냅니다.&amp;nbsp;이&amp;nbsp;속성은&amp;nbsp;Combine의&amp;nbsp;`CurrentValueSubject`나&amp;nbsp;`PassthroughSubject`와&amp;nbsp;같은&amp;nbsp;Publisher&amp;nbsp;타입입니다.&lt;br /&gt;&lt;br /&gt;2.&amp;nbsp;`.compactMap&amp;nbsp;{&amp;nbsp;$0&amp;nbsp;}`:&amp;nbsp;`selectedItem`의&amp;nbsp;값을&amp;nbsp;optional에서&amp;nbsp;non-optional로&amp;nbsp;변환하기&amp;nbsp;위해&amp;nbsp;`compactMap`&amp;nbsp;연산자를&amp;nbsp;사용합니다.&amp;nbsp;optional&amp;nbsp;값을&amp;nbsp;unwrapping하고,&amp;nbsp;non-optional&amp;nbsp;값만을&amp;nbsp;포함하는&amp;nbsp;스트림을&amp;nbsp;생성합니다.&lt;br /&gt;&lt;br /&gt;3.&amp;nbsp;`.receive(on:&amp;nbsp;RunLoop.main)`:&amp;nbsp;데이터&amp;nbsp;스트림을&amp;nbsp;메인&amp;nbsp;스레드에서&amp;nbsp;처리하도록&amp;nbsp;지정합니다.&amp;nbsp;UI&amp;nbsp;업데이트와&amp;nbsp;관련된&amp;nbsp;작업은&amp;nbsp;일반적으로&amp;nbsp;메인&amp;nbsp;스레드에서&amp;nbsp;수행되어야&amp;nbsp;하므로,&amp;nbsp;`receive(on:)`&amp;nbsp;연산자를&amp;nbsp;사용하여&amp;nbsp;메인&amp;nbsp;스레드에서&amp;nbsp;처리하도록&amp;nbsp;설정합니다.&lt;br /&gt;&lt;br /&gt;4.&amp;nbsp;`.sink&amp;nbsp;{&amp;nbsp;framework&amp;nbsp;in&amp;nbsp;...&amp;nbsp;}`:&amp;nbsp;데이터&amp;nbsp;스트림의&amp;nbsp;값을&amp;nbsp;받아&amp;nbsp;처리하는&amp;nbsp;부분입니다.&amp;nbsp;`sink`&amp;nbsp;연산자를&amp;nbsp;사용하여&amp;nbsp;데이터&amp;nbsp;스트림의&amp;nbsp;값을&amp;nbsp;받아옵니다.&amp;nbsp;클로저에서는&amp;nbsp;받아온&amp;nbsp;`framework`&amp;nbsp;값을&amp;nbsp;사용하여&amp;nbsp;`FrameworkDetailViewModel`을&amp;nbsp;생성하고,&amp;nbsp;해당&amp;nbsp;뷰&amp;nbsp;모델을&amp;nbsp;이용하여&amp;nbsp;`FrameworkDetailViewController`를&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;⭐️실제로 데이터를 처리하는 부분 &lt;br /&gt;위 코드에서는 viewController를 생성(FrameworkDetailViewModel)하고, 데이터를 넣어준다.&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;br /&gt;5.&amp;nbsp;`.store(in:&amp;nbsp;&amp;amp;subscriptions)`:&amp;nbsp;`sink`&amp;nbsp;연산자에서&amp;nbsp;반환된&amp;nbsp;`AnyCancellable`을&amp;nbsp;`subscriptions`&amp;nbsp;변수에&amp;nbsp;저장합니다.&amp;nbsp;`AnyCancellable`은&amp;nbsp;데이터&amp;nbsp;스트림의&amp;nbsp;구독을&amp;nbsp;취소할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;기능을&amp;nbsp;제공합니다.&amp;nbsp;해당&amp;nbsp;구독을&amp;nbsp;취소하려면&amp;nbsp;`subscriptions`&amp;nbsp;변수에&amp;nbsp;저장된&amp;nbsp;`AnyCancellable`&amp;nbsp;객체를&amp;nbsp;해제해야&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #6446ff; color: #ffffff; text-align: left;&quot;&gt;.store(in: &amp;amp;subscriptions)부분이 없으면?&lt;/span&gt;&lt;br /&gt;이&amp;nbsp;코드는&amp;nbsp;Combine을&amp;nbsp;사용하여&amp;nbsp;데이터&amp;nbsp;흐름을&amp;nbsp;처리하고,&amp;nbsp;`selectedItem`의&amp;nbsp;값이&amp;nbsp;변경되면&amp;nbsp;해당&amp;nbsp;값을&amp;nbsp;처리하는&amp;nbsp;방식을&amp;nbsp;보여줍니다.&amp;nbsp;`selectedItem`의&amp;nbsp;값이&amp;nbsp;변경되면&amp;nbsp;메인&amp;nbsp;스레드에서&amp;nbsp;`FrameworkDetailViewController`를&amp;nbsp;생성하고&amp;nbsp;표시하는&amp;nbsp;동작이&amp;nbsp;수행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.store(in:&amp;nbsp;&amp;amp;subscriptions)&amp;nbsp;부분이&amp;nbsp;없다면,&amp;nbsp;구독이&amp;nbsp;AnyCancellable&amp;nbsp;객체에&amp;nbsp;저장되지&amp;nbsp;않고&amp;nbsp;따로&amp;nbsp;참조되지&amp;nbsp;않게&amp;nbsp;됩니다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;경우,&amp;nbsp;구독이&amp;nbsp;유지되는&amp;nbsp;동안&amp;nbsp;AnyCancellable&amp;nbsp;객체가&amp;nbsp;존재하지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;구독을&amp;nbsp;취소하거나&amp;nbsp;해제할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;참조가&amp;nbsp;없게&amp;nbsp;됩니다.&amp;nbsp;따라서,&amp;nbsp;해당&amp;nbsp;구독을&amp;nbsp;취소하거나&amp;nbsp;해제할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;방법이&amp;nbsp;제한되며,&amp;nbsp;메모리&amp;nbsp;누수가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;&lt;br /&gt;따라서,&amp;nbsp;AnyCancellable&amp;nbsp;객체를&amp;nbsp;적절히&amp;nbsp;저장하는&amp;nbsp;것은&amp;nbsp;구독의&amp;nbsp;수명&amp;nbsp;주기를&amp;nbsp;관리하고&amp;nbsp;메모리&amp;nbsp;누수를&amp;nbsp;방지하기&amp;nbsp;위해&amp;nbsp;중요합니다.&amp;nbsp;.store(in:&amp;nbsp;&amp;amp;subscriptions)를&amp;nbsp;통해&amp;nbsp;AnyCancellable&amp;nbsp;객체를&amp;nbsp;subscriptions&amp;nbsp;변수에&amp;nbsp;저장함으로써,&amp;nbsp;필요한&amp;nbsp;시점에&amp;nbsp;구독을&amp;nbsp;취소하고&amp;nbsp;메모리를&amp;nbsp;해제할&amp;nbsp;수&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.store(in: &amp;amp;subscriptions) 외에도 구독을 관리하고 해제하는 다른 방법들이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #f2f7ff; color: #3b3f4e; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수동으로 구독 취소:&lt;span&gt;&amp;nbsp;&lt;/span&gt;AnyCancellable&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 직접 해제하는 방법입니다. 구독을 취소하기 위해&lt;span&gt;&amp;nbsp;&lt;/span&gt;cancel()&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드를 호출하면 됩니다. 예를 들어, 구독을 취소하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;AnyCancellable&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체를 해제하려면 다음과 같이 할 수 있습니다:&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1708519583478&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let cancellable = somePublisher.sink { value in
    // 처리 로직
}

// 구독 취소
cancellable.cancel()&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;Assign&amp;nbsp;연산자&amp;nbsp;사용:&amp;nbsp;@Published&amp;nbsp;속성이나&amp;nbsp;ObservableObject를&amp;nbsp;사용하는&amp;nbsp;경우,&amp;nbsp;assign(to:on:)&amp;nbsp;메서드를&amp;nbsp;사용하여&amp;nbsp;구독을&amp;nbsp;생성하고&amp;nbsp;속성에&amp;nbsp;값을&amp;nbsp;할당할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;이&amp;nbsp;경우,&amp;nbsp;AnyCancellable&amp;nbsp;객체를&amp;nbsp;따로&amp;nbsp;저장할&amp;nbsp;필요가&amp;nbsp;없습니다.&amp;nbsp;예를&amp;nbsp;들어:&lt;/p&gt;
&lt;pre id=&quot;code_1708519612550&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyViewModel: ObservableObject {
    @Published var someValue: Int = 0
    private var cancellable: AnyCancellable?

    init() {
        cancellable = somePublisher.assign(to: \.someValue, on: self)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 코드에서 assign(to:on:) 메서드를 사용하여 somePublisher의 값을 someValue에 할당하고, 구독을 생성합니다. cancellable 변수는 AnyCancellable 타입으로 선언되었지만, assign(to:on:) 메서드에 의해 자동으로 해제됨.&lt;/p&gt;</description>
      <category>Swift</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/475</guid>
      <comments>https://natatory.tistory.com/475#entry475comment</comments>
      <pubDate>Wed, 21 Feb 2024 21:49:01 +0900</pubDate>
    </item>
    <item>
      <title>URLSession: 서버와 소통하는 주체, Json Decode</title>
      <link>https://natatory.tistory.com/474</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;response status code가 200번대이어야함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708435605123&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;URLSession.shared.dataTaskPublisher(for: request)
            .tryMap { result -&amp;gt; Data in
                guard let response = result.response as? HTTPURLResponse,
                      (200..&amp;lt;300).contains(response.statusCode)
                else {
                    let response = result.response as? HTTPURLResponse
                    let statusCode = response?.statusCode ?? -1
                    throw NetworkError.responseError(statusCode: statusCode)
                }
                return result.data
            }&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_1708435687802&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                print(&quot;Error: \(error.localizedDescription)&quot;)
                return
            }
            
            if let data = data {
                do {
                    //Data를 decode, 형식이 MyData이다.
                    let decodedData = try JSONDecoder().decode(MyData.self, from: data)
                    DispatchQueue.main.async {
                        self.data = decodedData
                    }
                } catch {
                    print(&quot;Error decoding data: \(error.localizedDescription)&quot;)
                }
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data를 decode, 형식이 MyData이다.&lt;/p&gt;</description>
      <category>code</category>
      <category>전대</category>
      <author>Lyla.</author>
      <guid isPermaLink="true">https://natatory.tistory.com/474</guid>
      <comments>https://natatory.tistory.com/474#entry474comment</comments>
      <pubDate>Tue, 20 Feb 2024 22:29:02 +0900</pubDate>
    </item>
  </channel>
</rss>