<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hyun9iOS</title>
    <link>https://gyuios.tistory.com/</link>
    <description>  I&amp;rsquo;m currently learning iOS.</description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 22:46:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hyun99999</managingEditor>
    <image>
      <title>hyun9iOS</title>
      <url>https://tistory1.daumcdn.net/tistory/4844491/attach/265f5a56bb0b45dd961bd4f604cb2ddd</url>
      <link>https://gyuios.tistory.com</link>
    </image>
    <item>
      <title>iOS) UIScreen.main deprecated 를 대체하여 오픈 소스 라이브러리 컨트리뷰터 되어보기</title>
      <link>https://gyuios.tistory.com/309</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/295&quot;&gt;iOS) YPImagePicker error : Stored properties cannot be marked unavailable with &lt;code&gt;@available&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 15 업데이트 이후 YPImagePicker 에서 사용하려하니 에러를 만났고, 하룻동안은 빌드 없이 코드를 봤던 기억이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 몇일 뒤 CocoaPods 를 업데이트하고 나서 빌드 오류가 나지 않아 YPImagePicker 깃허브를 들어갔고 새로운 버전이 릴리즈된 것을 확인하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거창한 문서도 거창한 코드 변화도 아닌 필요에 의한 이제는 사용하지 않는 변수의 단순한 코드 삭제였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 소스 라이브러리를 사용만 하다보니 사용자 입장에서 수동적으로 대응한 것 같다는 생각이 들어서 내가 할 수 있는 부분이 있다면 적극적으로 적극적으로 기여해보고 싶다는 생각이 들었습니다. 이번 기회를 통해서 몇 군데에 PR 을 작성해볼 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가기 전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC2022 &lt;b&gt;&lt;b&gt;What's new in UIKit 세션에서 다음과 같이 청천벽력같은.. 소식을 보았습니다.&lt;/b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/909a4417-6ccf-4e98-9186-3a9c80112bfc&quot; alt=&quot;1&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처 : &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2022/10068/?time=1121&quot;&gt;https://developer.apple.com/videos/play/wwdc2022/10068/?time=1121&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게두고 있다가 시간이 흘러 iOS 15에서 iOS 17까지 시간이 흘렀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 16까지만 지원한다고 하니 이를 대응해야겠다고 생각해서 글을 작성하였습니다.(하지만, iOS 17시뮬레이터에서도 정상적으로 동작하였습니다.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/9c33d1ae-612d-4472-baa3-e413f462b818&quot; alt=&quot;2&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiscreen/1617815-main&quot;&gt;https://developer.apple.com/documentation/uikit/uiscreen/1617815-main&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/09d1d5f5-c8e5-434c-94e7-a5f969e31360&quot; alt=&quot;3&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Xcode 에서 해당 프로퍼티에 접근하면 경고 메시지를 확인할 수 있었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 다음과 같이 디바이스 스크린에 접근할 수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;UIScreen.main.bounds.width&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(배경을 살펴보자면 사용할 수 있는 Scene 들이 많아졌습니다. 그래서 해당 Scene 들에 접근하는 방법으로 대체하였습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wwdc 일부를 발췌해보기&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 개발자 문서에서 예시를 든 코드
view.window?.windowScene?.screen.bounds

// UIApplication 을 활용한 접근
private let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.screen.bounds&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진행 중인 프로젝트를 확인해보니 이렇게 많은 곳에서 아직 UIScreen.main 을 사용하고 있었고, 이에 기여해보고자 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/1d8d1250-7c20-4ef5-82bc-f4c79876ac57&quot; alt=&quot;4&quot; width=&quot;300&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  초기에 생각했던 유의할 점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UIScreen.mian 타입 프로퍼티는 iOS 16까지만 사용하고 곧 deprecated 된다.&lt;/li&gt;
&lt;li&gt;UIWindowScene 은 iOS 13부터 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;오픈 소스 라이브러리의 최소 버전을 확인하여 OS분기처리가 예상됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PinLayout
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 12.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;YPImagePicker
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 12.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kingfisher
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 12.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;lottie-ios
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 11.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;KakaoSDKCommon
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포를 위한 목적으로 운영하기 때문에 이슈와 PR 을 지원하지 않는다고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IQKeyboardManager
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 11.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PryntTrimmerView
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cocoapods deployment_target : 9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해보니 대부분이 iOS 12 를 최소 지원 버전으로 가지기 때문에 iOS 13 이후에만 UIWindowScene 을 사용하도록 코드를 수정해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 모든 라이브러리에서 아직 UIScreen.main 관련된 이슈와 PR 이 없기 때문에 작성해보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  PR 작성 및 Issue 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Issue 템플릿을 사용하고 있거나 PR 템플릿 주석에서 이슈를 예시로 드는 레포지토리에서는 이슈를 만들어서 진행하였습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;다음은 코드리뷰를 거쳐 최종적으로 커밋된 코드입니다.(2023.11.21 기준)&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PinLayout&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/layoutBox/PinLayout/pull/275/files&quot;&gt;https://github.com/layoutBox/PinLayout/pull/275/files&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/280b6a25-6b9d-4f14-8e7d-1faac8b93712&quot; alt=&quot;1&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;YPImagePicker&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Yummypets/YPImagePicker/pull/805&quot;&gt;https://github.com/Yummypets/YPImagePicker/pull/805&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/800c3301-f5f3-4092-905e-d2fdfb308627&quot; alt=&quot;2&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kingfisher&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/onevcat/Kingfisher/pull/2152&quot;&gt;https://github.com/onevcat/Kingfisher/pull/2152&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/1b8fc8e4-eb8d-4941-8eb0-20d26bc75c7a&quot; alt=&quot;3&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lottie-ios&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/airbnb/lottie-ios/pull/2216&quot;&gt;https://github.com/airbnb/lottie-ios/pull/2216&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/3ebc2ae7-691b-48cd-aec9-ecd0d4918d65&quot; alt=&quot;4&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IQKeyboardManager&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/hackiftekhar/IQKeyboardManager/pull/1989&quot;&gt;https://github.com/hackiftekhar/IQKeyboardManager/pull/1989&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/4b9ce298-1b5b-483e-bcad-65059a8e3575&quot; alt=&quot;5&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PrynTrimmerView&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/HHK1/PryntTrimmerView/pull/93&quot;&gt;https://github.com/HHK1/PryntTrimmerView/pull/93&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/d23b008b-8dd1-41e3-8dca-7eaab9421739&quot; alt=&quot;6&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  코드 리뷰 - lottie-ios&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lottie-ios 에서 코드 리뷰가 달렸다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렴풋이 내 PR 에 과연 리뷰를 달아줄까 반신반의하였고, UIScreen.main deprecated 된다는데? 대안은 이거야! 라고 하는 의견에 얼마나 적극적인 코드 리뷰가 달릴까 싶어서 코드의 품질보다 PR 작성에 열을 올렸었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 설명 없이도 이해할 수 있는 것이 가장 좋은 질의 코드 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 내가 놓친 부분들을 리뷰 받았다.&lt;i&gt;(이제라도 잘할게요! )&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nil 인 경우(사실 없지 않을까! 하지만 값이 옵셔널이기때문에 force binding 을 하고 싶지는 않았다.).zero 를 반환하도록 했었다. 더 나은 의견이 없는지 물었고, 다음과 같이 답을 남겨두었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/31ae29a6-e12c-4f10-91b3-109518dd97e1&quot; alt=&quot;7&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 라이브러리는 tvOS 에서도 사용되었고, &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiscreen/1617815-main&quot;&gt;main&lt;/a&gt;문서를 다시 살펴보니 다른 os 들도 이제서야 눈에 들어왔다.(iOS 2.0-16.0, iPadOS 2.0-16.0, Mac Catalyst 13.1-16.0, tvOS 9.0-16.0 Deprecated)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/7baada4d-5be7-4346-931d-1e86315ade05&quot; alt=&quot;8&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 다른 PR도 필요한 경우에 수정하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, CI 과정에서 Lint 에서 fail 을 받았고 아래를 참고해서 code format 을 맞춘 뒤에 진행하면 되겠습니다.(리뷰어가 저는 커밋해주었습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고 : &lt;a href=&quot;https://github.com/airbnb/lottie-ios/blob/master/README.md#contributing&quot;&gt;https://github.com/airbnb/lottie-ios/blob/master/README.md#contributing&lt;/a&gt;)&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;// % sudo gem install bundle
// bundle 설치

% bundle exec rake format:swift&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;blocks are indented with 4 spaces instead of 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/c86f036e-1893-415a-92fe-b69bd035a20e&quot; alt=&quot;9&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 Test 과정에서 스냅샷을 확인한 후에 리뷰어가 window 가 nil 되는 경우를 가져왔다..!(와우 없을 줄 알았는데 이렇게 코딩하면 안되겠다는 생각이 팍 들었다.)리뷰를 남겨주었고 이를 함께 해결하기도 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정이 궁금하시다면 아래 PR 에서 확인할 수 있습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/airbnb/lottie-ios/pull/2216&quot;&gt;https://github.com/airbnb/lottie-ios/pull/2216&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장의 에러가 아니기 때문에 PR 에 대한 관심이 낮을 수도 있다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시나 해당 이슈를 가볍게 생각해서 반신반의하며 PR 을 올렸던 것 같다. 이에 진심으로 리뷰해주자 조금 부끄러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 또 뭐 어떤가 서로 의견을 교환하며 놓친 부분들을 채우고 더 나은 코드로써 보답하면 되지 않나 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 품질이 우선인데 너무 쫄아있었던 것이 아니었나싶다. 평소 내 프로젝트에 코드를 작성하듯이 적극적으로 코드를 작성하는 자세를 가져도 아무런 문제가 없다고 느꼈다.&lt;/p&gt;</description>
      <category>iOS</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/309</guid>
      <comments>https://gyuios.tistory.com/309#entry309comment</comments>
      <pubDate>Tue, 21 Nov 2023 23:15:19 +0900</pubDate>
    </item>
    <item>
      <title>iOS) viewIsAppearing 대해 알아보자 - 적잘한 시점에서 키보드 활성화하기</title>
      <link>https://gyuios.tistory.com/308</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트러블 슈팅을 살펴보고 viewIsAppearing 에 대해서 알아보자&lt;/li&gt;
&lt;li&gt;이를 사용해서 트러블 슈팅을 해결해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  트러블 슈팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;문제 상황)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 17 - medium, custom detent 모두 viewWillAppear 에서 키보드를 활성화해도 원하는 동작 함.(custom detent 는 iOS 16이상부터 사용 가능)&lt;/li&gt;
&lt;li&gt;iOS 15 - medium detent 를 설정하고 viewWillAppear 에서 키보드를 활성화시키면 다음과 같이 automatic keyboard avoidance 가 적용되지 않는 것 같습니다.&lt;/li&gt;
&lt;li&gt;iOS 17 에서는 medium 이어도 정상적으로 키보드가 뷰를 밀어내는 반면 iOS 15 에서는 그렇지 못했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/d7531add-35e7-40df-948d-1ebd5f3a2b48&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 버전이 다름에서 오는 차이는 어쩔 수 없으니 해결을 위해서 우선, 키보드를 활성화시키는 타이밍이 문제라고 생각했습니다. viewDidAppear 에서 활성화하니 반응이 늦어 적용하기 힘들겠다고 생각했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/d77a32a8-e31d-4044-9f48-6c4c369fae22&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC23 에서 소개된 &lt;b&gt;viewIsAppearing&lt;/b&gt; 을 사용해서 해결해보기로 하였습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;viewIsAppearing&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiviewcontroller/4195485-viewisappearing&quot;&gt;viewIsAppearing(_:) | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/08bfa6e0-0474-460b-b913-8bc48b7cc0b5&quot; alt=&quot;3&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewWillAppear(:) 호출 후 뷰 컨트롤러의 뷰가 나타날 때마다 이 메소드를 호출합니다. viewWillAppear(:) 와 달리 시스템은 뷰 컨트롤러의 뷰를 뷰 계층에 추가한 후 이 메소드를 호출하고, superview 는 뷰를 배치합니다. &lt;b&gt;시스템이 호출할 때 즘이면 뷰 컨트롤러와 뷰 모두 trait collections 를 수신하고 뷰는 정확한 geometry(size, safe area 등)를 가집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메소드를 override 해서 뷰 표시와 관련된 커스텀 동작을 수행할 수도 있습니다. &lt;b&gt;예를 들어,&lt;/b&gt; 뷰 또는 뷰 컨트롤러의 trait collections 를 기반으로 뷰를 구성하거나 업데이트할 수 있습니다. 또는 scroll position 계산은 view&amp;rsquo;s size 와 geometry 에 따라 달라지기 때문에, 뷰가 appear 할 때 collection/table view 를 programmatically scroll 해서 선택한 셀이 표시되도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Choosing Appropriate&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 viewWillAppear(_:) 를 호출한 후에 이 메소드를 호출하지만, 두 콜백은 모두 같은 CATransaction 에서 발생합니다. 즉, 두 메소드에서의 변경이 동시에 사용자에게 보여지게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/7025b35f-6a8c-4d4e-8e78-adc5ea733128&quot; alt=&quot;4&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;viewWillAppear(:) 를 호출할 때는 traits 와 geometry 가 최신 상태가 아니지만, viewIsAppearing(:) 을 호출할 때는 최신 상태이므로 뷰를 업데이트할 때 사용할수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 경우에만 viewWillAppear(_:) 사용하세요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;alognside animations 을 추가하기 위해 transitionCoordinator 에 접근할 때와 같이 view transition 이 시작되기 전에 콜백이 필요합니다. alongside animations 는 프레임워크가 뷰 컨트롤러 transition animations 과 동시에 수행하도록 지시하는 애니메이션입니다.&lt;/li&gt;
&lt;li&gt;뷰 컨트롤러 또는 뷰의 traits, hierarchy, or geometry 의존하지 않는 작업을 수행할 때 콜백이 필요합니다. viewWillAppear(:) 에서 database notifications 를 등록하거나 viewDidDisappear(:) 에서 등록 해제가 이에 해당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 모든 경우에는 &lt;b&gt;viewIsAppearing(_:)&lt;/b&gt; 을 사용해서 뷰를 업데이트할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/8c0aec4e-77c4-4a13-8441-6b4363fe343f&quot; alt=&quot;5&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템은 뷰가 layoutSubviews() 를 실행할 때마다 viewWillLayoutSubviews() 와 viewDidLayoutSubviews() 와 같은 레이아웃 메소드를 호출합니다. 이는 뷰가 전환되는 중이거나 표시되는 동안 여러 번 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만, 시스템은 뷰가 보여질 때 viewIsAppearing(_:) 을 한 번만 호출하고, 레이아웃이 필요없는 경우에도 이를 호출합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰 컨트롤러가 뷰 계층에 추가하는 방법과 발생하는 메시지의 순서에 대해서 좀 더 자세히 알아보기 위해 다음 문서를 살펴볼 수 있습니다. &lt;a href=&quot;https://developer.apple.com/documentation/uikit/view_controllers/displaying_and_managing_views_with_a_view_controller&quot;&gt;Displaying and managing views with a view controller&lt;/a&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;WWDC23 &amp;gt; &lt;/span&gt;&lt;a style=&quot;letter-spacing: 0px;&quot; href=&quot;https://developer.apple.com/videos/play/wwdc2023/10055/&quot;&gt;What's new in UIKit&lt;/a&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 에서도 몇 부분을 발췌해보겠습니다.(2분 23초)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC23 에서 UIViewController.viewIsAppearing(_:) 새로운 view controller callback 이 생겼습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/fdb5ebcd-4389-414f-9739-6f7210b0142b&quot; alt=&quot;6&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 문서에서도 보았던 흐름도입니다. 몇 가지를 중요하게 강조하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/6a6bfd53-60da-4a60-970d-fb05ccbafc78&quot; alt=&quot;7&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;viewWillAppear(_:)가 호출되는 시점을 살펴보게 되면 trait collection 을 사용하거나 뷰의 크기와 geometry 구조에 의존하기에 이른 것을 볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;transition animaties 후에 마지막 transaction 에서 viewDidAppear(_:)가 호출되는 시점을 주목해보겠습니다. transaction 이 완료되기 전까지는 viewDidAppear(_:)의 변화가 눈에 보이지 않는다는 것이고, 보이게 하고 싶기에는 너무 늦었다는 뜻입니다.&lt;br /&gt;반면에 viewIsAppearing(_:)은 viewWillAppear(_:)과 동일한 transaction에서 호출됩니다. 즉, 두 개의 콜백에서의 변화는 동시에 사용자 눈에 보이게 된다는 것입니다. 전환의 첫 프레임부터 가능합니다.&lt;/li&gt;
&lt;li&gt;viewIsAppearing(_:)과 layout call back(viewWillLayoutSubviews(), viewDidLayoutSubviews()) 사이에는 주요한 차이가 있습니다.&lt;br /&gt;뷰의 layoutSubviews() 실행 때마다 layout callback 이 이루어지는데 전환 중에 여러번 혹은 나중에 뷰가 보일 때 언제든지 일어날 수 있습니다. 하지만, viewIsAppearing(_:)은 전환 중에 한 번만 호출되고 뷰에 레이아웃이 필요 없더라도 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정리해보자면&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;viewIsAppearing&lt;/b&gt; 은 viewWillAppear 호출 후에 뷰 컨트롤러의 뷰를 뷰 계층에 추가할 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;같은 transaction 에서 호출되기 때문에 두 콜백에서의 변경이 동시에 사용자에게 첫 프레임부터 보여지게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;viewIsAppearing&lt;/b&gt; 은 viewWillAppear 와 달리 뷰 컨트롤러와 뷰 모두 최신 상태의 trait collection 을 수신하고 뷰는 geometry(size, safe area 등)을 가집니다.&lt;/li&gt;
&lt;li&gt;뷰가 layoutSubviews() 를 실행할 때마다 viewWillLayoutSubviews() 와 viewDidLayoutSubviews() 와 같은 레이아웃 콜백을 호출합니다. 이는 뷰가 전환되는 중이거나 표시되는 동안 여러 번 발생할 수 있습니다. 하지만, 시스템은 뷰가 보여질 때 &lt;b&gt;viewIsAppearing&lt;/b&gt; 을 한 번만 호출하고, 레이아웃이 필요없는 경우에도 이를 호출합니다.&lt;/li&gt;
&lt;li&gt;사용하는 예시)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;triat collection 을 기반으로 뷰를 구성하거나 업데이트&lt;/li&gt;
&lt;li&gt;뷰가 보여질 때 collection/table view 를 programmatically scroll 해서 해당 셀이 보여지도록 scroll position 을 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용해보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;override func viewIsAppearing(_ animated: Bool) {
    super.viewIsAppearing(animated)

    textFiled.becomeFirstResponder()
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 15, 17 모두 원하는 대로 작동하게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/b0b3f5de-a95b-41ba-ad83-2bb58867d717&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>IOS</category>
      <category>viewIsAppearing</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/308</guid>
      <comments>https://gyuios.tistory.com/308#entry308comment</comments>
      <pubDate>Tue, 21 Nov 2023 20:02:23 +0900</pubDate>
    </item>
    <item>
      <title>iOS) containerBackgroundRemovable(_:) 알아보기(feat. iPad lock screen)</title>
      <link>https://gyuios.tistory.com/307</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글에서 iOS 17부터 &lt;b&gt;containerBackground(for:)&lt;/b&gt; modifier 를 통해서 container background 을 설정해주어야 한다고 했어요.&lt;/p&gt;
&lt;figure id=&quot;og_1699395846648&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;iOS) iOS 17 Widget error - Please adopt containerBackground API 해결하기(remove iOS 17 widet extra padding)&quot; data-og-description=&quot;  내용 iOS 17 containerBackground API error 와 extra padding 이 생기는 문제를 해결해 보겠습니다. iOS 17 적용 후에 잠금화면 위젯이 제대로 보이지 않는 이슈가 발생했습니다. Xcode 에서 한 번 살펴보겠습&quot; data-og-host=&quot;gyuios.tistory.com&quot; data-og-source-url=&quot;https://gyuios.tistory.com/306&quot; data-og-url=&quot;https://gyuios.tistory.com/306&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gXYdL/hyUrzG03b9/fUFVWKXE5F1tysI34xo1AK/img.png?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/u5AQj/hyUrqXB94V/DMKBlp6VO4eibkICk9mrYK/img.png?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/IWVHd/hyUrD3IVFA/7Sr5JcJbm8bKlvKk17IAjK/img.png?width=1700&amp;amp;height=922&amp;amp;face=0_0_1700_922&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/306&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gyuios.tistory.com/306&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gXYdL/hyUrzG03b9/fUFVWKXE5F1tysI34xo1AK/img.png?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/u5AQj/hyUrqXB94V/DMKBlp6VO4eibkICk9mrYK/img.png?width=800&amp;amp;height=385&amp;amp;face=0_0_800_385,https://scrap.kakaocdn.net/dn/IWVHd/hyUrD3IVFA/7Sr5JcJbm8bKlvKk17IAjK/img.png?width=1700&amp;amp;height=922&amp;amp;face=0_0_1700_922');&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;iOS) iOS 17 Widget error - Please adopt containerBackground API 해결하기(remove iOS 17 widet extra padding)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  내용 iOS 17 containerBackground API error 와 extra padding 이 생기는 문제를 해결해 보겠습니다. iOS 17 적용 후에 잠금화면 위젯이 제대로 보이지 않는 이슈가 발생했습니다. Xcode 에서 한 번 살펴보겠습&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gyuios.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그렇다면 아래와 같이 딱히 background 가 필요하지 않는 경우에는 어떻게 할까요?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/8adfc3cb-2301-4582-8ff0-18cea7ed7d83&quot; alt=&quot;1&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: WWDC 23 &amp;gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10027/&quot;&gt;Bring widgets to new places&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그때는 containerBackgroundRemovable(_:) 를 사용하면 되는데 코드를 살펴보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// ...
// iOS 17부터는 이 modifier 가 무조건적으로 들어가야 한다.
// StandBy 모드 등 사용 장소에 따라서 background 를 대응하기 위해서 iOS 17 부터 적용
    .containerBackground(for: .widget) {
        Color.white
    }
}

struct MyCardWidget: Widget {
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: &quot;...&quot;,
                            intent: MyCardIntent.self,
                            provider: MyCardProvider()) { entry in
            MyCardEntryView(entry: entry)
        }
        .configurationDisplayName(&quot;...&quot;)
        .description(&quot;...&quot;)
        .supportedFamilies([.systemSmall])
        // ✅ 지도, 사진 등 뚜렷한 foreground 콘텐츠가 없다면 제거할 background 가 없습니다.
        // 이런 경우에 false 로 설정해서 사용.
        .containerBackgroundRemovable(false)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 문서를 통해서 해당 modifier 를 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/widgetconfiguration/containerbackgroundremovable(_:)&quot;&gt;containerBackgroundRemovable(_:) | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/71c0717a-2c51-4beb-8452-51dc78856c03&quot; alt=&quot;2&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isRemovable 가 true 인 경우 background 를 선호하지 않는 context 에서 container background 의 제거를 지원합니다. false 인 경우 시스템이 background 를 지우지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;true 기본값 파라미터를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WidgetConfiguration 을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Discussion :&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 경우, 가능한 많은 context 에 위젯을 배치할 수 있도록 위젯의 background container 를 제거할 수 있도록 합니다. nonremovable 로 설정하면 다양한 상황에서 위젯이 부적격해집니다. &lt;b&gt;예를 들어, removable background 가 없는 small widget 은 iPad lock screen 에 표시되지 않습니다.(아래에서 확인해보겠습니다.)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;background 를 nonremovable 로 설정하면 시스템은 항상 위젯의 background container 를 표시합니다. 이때 background 는 색이 faded 또는 desaturated(채도가 낮다) 해보일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 modifier 는 iOS 17, watchOS 10, macOS 14 이전의 버전에는 영향을 미치지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;b&gt;containerBackgroundRemovable(true)&lt;/b&gt; 로 설정했을 때 대표적인 예시는 날씨 위젯입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/a9beeac6-9434-4a4b-b1e1-74bf13ea4021&quot; alt=&quot;3&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: WWDC 23 &amp;gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10027/&quot;&gt;Bring widgets to new places&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iPhone lock screen 에서는 accessory widget 형태들만 잠금화면에서 노출되었습니다. 그런데 samll widget 에 removable background 를 설정하면 iPad lock screen 에 노출된다는데 확인해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  iPad Lock Screen small widget 살펴보기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시뮬레이터를 iPad 로 설정하게되면 &amp;ldquo;Canvas Device Settings&amp;rdquo; 에서 Small Lock Screen 을 설정할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/4173a11c-a737-457c-a119-f37de74bbc62&quot; alt=&quot;4&quot; width=&quot;350&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;containerBackground(for:) 로 background 를 설정하고 containerBackgroundRemovable() modifier 로 &lt;b&gt;removable background&lt;/b&gt; 로 설정해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;struct EmptyMyCardView: View {

    // ...

    var body: some View {
// ✅ background 를 containerBackground(for:) 로 대체
//        ZStack {
//            Color.white
            VStack() {
                Text(&quot;아직 내 명함이 없어요 &quot;)
                    // ...
                HStack {
                    Text(&quot;명함 만들러 가기&quot;)
                        // ...
                }
            }
//      }
            .containerBackground(for: .widget) {
                Color.white
            }
    }
}

struct EmptyMyCardView: Widget {
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: &quot;...&quot;,
                            intent: MyCardIntent.self,
                            provider: MyCardProvider()) { entry in
            EmptyMyCardView(entry: entry)
        }
        // ...
        // ✅
        .containerBackgroundRemovable(false)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석처럼 ZStack 을 사용해서 background 를 설정하면(containerBackground(for:) 에서 아무것도 설정하지 않음) 다음과 같이 부적합한 위젯이 만들어졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/2e72239f-017c-4c31-bf50-b1578f07bda7&quot; alt=&quot;5&quot; width=&quot;200&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;containerBackground(for:) 로 background 을 적용해보았고, 적합한 위젯이 만들어졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/86a13b5d-59eb-49c7-96dc-b8413ad476cf&quot; alt=&quot;6&quot; width=&quot;200&quot; /&gt;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>containerBackground</category>
      <category>containerBackgroundRemovable</category>
      <category>IOS</category>
      <category>Widget</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/307</guid>
      <comments>https://gyuios.tistory.com/307#entry307comment</comments>
      <pubDate>Tue, 7 Nov 2023 23:20:46 +0900</pubDate>
    </item>
    <item>
      <title>iOS) iOS 17 Widget error - Please adopt containerBackground API 해결하기(remove iOS 17 widet extra padding)</title>
      <link>https://gyuios.tistory.com/306</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 17 containerBackground API error 와 extra padding 이 생기는 문제를 해결해 보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 17 적용 후에 잠금화면 위젯이 제대로 보이지 않는 이슈가 발생했습니다. Xcode 에서 한 번 살펴보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-11-08 오전 6.43.17.png&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjbvJE/btszUqEdaYO/6dJWeKFAD4PnVl8lNyA6B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjbvJE/btszUqEdaYO/6dJWeKFAD4PnVl8lNyA6B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjbvJE/btszUqEdaYO/6dJWeKFAD4PnVl8lNyA6B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjbvJE%2FbtszUqEdaYO%2F6dJWeKFAD4PnVl8lNyA6B0%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;450&quot; height=&quot;332&quot; data-filename=&quot;스크린샷 2023-11-08 오전 6.43.17.png&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xcode 에서 preview 로 확인해보려했더니 해당 경고가 등장했습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/c0fbfcba-3f39-4700-9013-92e5aae70040&quot; alt=&quot;2&quot; width=&quot;450&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;Ensure that you have called the containerBackground(for: .widget) {&amp;hellip;} modifier in your widget view.&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  containerBackground(for:alignment:content:)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 문서를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/containerbackground(for:alignment:content:)&quot;&gt;containerBackground(for:alignment:content:) | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/b86f565e-b575-4dad-ab96-bf01e6870fa7&quot; alt=&quot;3&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰를 사용해서 둘러싸는 container 의 container background 를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;Parameters&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;alignment :&lt;/b&gt;The alignment that the modifier uses to position the implicit ZStack that groups the background views. The default is center.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;container :&lt;/b&gt;The container that will use the background.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;content :&lt;/b&gt; The view to use as the background of the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;Discussion&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 modifier 는 전체 parent container 를 자동으로 채우는 점에서 &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/view/background(_:ignoressafeareaedges:)&quot;&gt;background(_:ignoresSafeAreaEdges:)&lt;/a&gt; 와 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;적용해 보겠습니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;if #available(iOSApplicationExtension 17.0, *) {
    Image(&quot;widgetEmpty&quot;)
         .resizable()
         .scaledToFill()
         .containerBackground(for: .widget) {
             // ...    
         }
} else {
     Image(&quot;widgetEmpty&quot;)
         .resizable()
         .scaledToFill()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제야 프리뷰에서 에러 없이 확인할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;background 를 사용하지 않는 경우라도 containerBackground(for:) modifier 를 사용해야 에러를 해결할 수 있었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/887dd517-a025-4cca-a6b9-1c7042a2666e&quot; alt=&quot;4&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  ??? 저 여백은 뭐지?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  트러블 슈팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;그런데 아직 완성은 아닙니다! 에러가 나지 않을 뿐이지 전에는 없던 padding 이 생겼습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 커뮤니티에서도 iOS 17 widget extra padding 에 대한 질문들이 올라와 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/5827ef77-0519-48eb-92b1-125632949f66&quot; alt=&quot;5&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  contentMarginsDisabled&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/swiftui/widgetconfiguration/contentmarginsdisabled()&quot;&gt;contentMarginsDisabled() | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WWDC 23 &amp;gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10027/&quot;&gt;Bring widgets to new places&lt;/a&gt; 에서 &lt;b&gt;contentMarginsDisabled()&lt;/b&gt; 를 사용해서 safe area 를 비활성화할 수 있다고 안내하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 문서를 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/7858f3bd-3d6a-4e4f-950e-859f93125881&quot; alt=&quot;6&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default content margins 이라고 표현하는 것을 봐서 기본적으로 설정되는 값을 비활성화하는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Return Value :&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default content margins 를 사용하지 않는 widget configuration 을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Discussion :&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;content margins 을 비활성화하면 위젯의 content 주위에 마진을 자동으로 추가하지 않습니다. custom margins 을 지정하기 위해서 &lt;a href=&quot;https://developer.apple.com/documentation/swiftui/environmentvalues/widgetcontentmargins&quot;&gt;widgetContentMargins&lt;/a&gt; 를 padding() 과 함께 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 modifier 는 iOS 17, watchOS 10, macOS 14 이전의 버전에서는 영향을 미치지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(즉, 별도의 if #available 구문을 사용하지 않아도 됩니다!)&lt;/i&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;반환 값이 WidgetConfiguration 입니다.&lt;/b&gt; 잠시 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;containerBackground(for:) 처럼 View 를 반환하는 것이 아닌 &lt;b&gt;WidgetConfiguration&lt;/b&gt; 을 반환하기 때문에 사용하는 위치가 다릅니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;struct ExampleEntryView: View {
    // ...

    var body: some View {
        if #available(iOSApplicationExtension 17.0, *) {
            Text(&quot;Example&quot;)
                // ...
                // ✅
                .containerBackground(for: .widget) { }
        } else {
            Text(&quot;Example&quot;)
                // ...
    }
}

struct ExampleWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind,
                            provider: Provider()) { entry in
            ExampleEntryView(entry: entry)
        }
        .configurationDisplayName(&quot;...&quot;)
        .description(&quot;...&quot;)
        .supportedFamilies([.systemSmall])
        // ✅
        .contentMarginsDisabled()
        .containerBackgroundRemovable(false)
    }
}
//   preview 에서는 아직도 content margin 이 있는데요..???

//   preview 에서는 `contentMarginsDisabled()` 를 사용하지 않았기 때문에 그런 것 같습니다.
struct ExampleWidget_Previews: PreviewProvider {
    static var previews: some View {
        // View 를 사용
        ExampleEntryView(entry: ExampleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preview 에서 content margin 이 보이더라도 실제로 빌드해보면 content margin 이 사라짐을 확인 할 수 있습니다!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/3cd5f56d-4461-4a8d-9aaa-e2556336e0fc&quot; width=&quot;200&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;혹시나해서 iPhone 에 깔려있는 앱들을 살펴봤습니다. KREAM(크림 사랑합니다 ), Instagram(인스타 사랑합니다 ) 앱에서 content margins 을 볼 수 있었어요.. 아마 이것때문에 레이아웃이 좀 어긋난 것처럼도 보였습니다.(2023-11-08 기준)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-11-08 오전 6.58.53.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6qZi2/btszZF0Fgti/3EIsG1sQtWRkxKASH57MA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6qZi2/btszZF0Fgti/3EIsG1sQtWRkxKASH57MA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6qZi2/btszZF0Fgti/3EIsG1sQtWRkxKASH57MA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6qZi2%2FbtszZF0Fgti%2F3EIsG1sQtWRkxKASH57MA1%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;1700&quot; height=&quot;922&quot; data-filename=&quot;스크린샷 2023-11-08 오전 6.58.53.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 많은 앱들의 위젯들은 이미 대응하고 있었습니다. &quot;Please adopt containerBackground API&quot; error 들은 찾아볼 수도 없더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 진행하는 프로젝트도 이번 업데이트에서 대응 예정입니다! 혹시나 에러를 보고 싶으시면 설치해서 위젯으로 확인해보세요  &lt;/p&gt;
&lt;figure id=&quot;og_1699395648983&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;lrm;나다 NADA - 나를 다 담은 명함 서비스&quot; data-og-description=&quot;&amp;lrm;이제는 나다로 명함을 교환하며 쉽고 재미있게 서로를 알아가 보세요. #1. 직장에서의 나도, 친구들 사이의 나도, 덕질할 때의 나도 모두 나니까! - 내 속엔 내가 너무나 많아! 직장/덕질/기본 &quot; data-og-host=&quot;apps.apple.com&quot; data-og-source-url=&quot;https://apps.apple.com/kr/app/%EB%82%98%EB%8B%A4-nada-%EB%82%98%EB%A5%BC-%EB%8B%A4-%EB%8B%B4%EC%9D%80-%EB%AA%85%ED%95%A8-%EC%84%9C%EB%B9%84%EC%8A%A4/id1600711887&quot; data-og-url=&quot;https://apps.apple.com/kr/app/%EB%82%98%EB%8B%A4-nada-%EB%82%98%EB%A5%BC-%EB%8B%A4-%EB%8B%B4%EC%9D%80-%EB%AA%85%ED%95%A8-%EC%84%9C%EB%B9%84%EC%8A%A4/id1600711887&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pWC9S/hyUrwpX8PQ/GkYNGhuaeCGdROCKU7fRKk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/z0JIj/hyUrzNKXtn/4d6cIfKu0YKnn57g2yvvmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://apps.apple.com/kr/app/%EB%82%98%EB%8B%A4-nada-%EB%82%98%EB%A5%BC-%EB%8B%A4-%EB%8B%B4%EC%9D%80-%EB%AA%85%ED%95%A8-%EC%84%9C%EB%B9%84%EC%8A%A4/id1600711887&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://apps.apple.com/kr/app/%EB%82%98%EB%8B%A4-nada-%EB%82%98%EB%A5%BC-%EB%8B%A4-%EB%8B%B4%EC%9D%80-%EB%AA%85%ED%95%A8-%EC%84%9C%EB%B9%84%EC%8A%A4/id1600711887&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pWC9S/hyUrwpX8PQ/GkYNGhuaeCGdROCKU7fRKk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/z0JIj/hyUrzNKXtn/4d6cIfKu0YKnn57g2yvvmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;나다 NADA - 나를 다 담은 명함 서비스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lrm;이제는 나다로 명함을 교환하며 쉽고 재미있게 서로를 알아가 보세요. #1. 직장에서의 나도, 친구들 사이의 나도, 덕질할 때의 나도 모두 나니까! - 내 속엔 내가 너무나 많아! 직장/덕질/기본&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;apps.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;깃허브&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1699395671740&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - TeamNADA/NADA-iOS-ForRelease: 릴리즈를 목표로 하는 민재이준현규의 으라차차  대소동   iOS&quot; data-og-description=&quot;릴리즈를 목표로 하는 민재이준현규의 으라차차  대소동  iOS❤️&amp;zwj;  TeamNADA. Contribute to TeamNADA/NADA-iOS-ForRelease development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease&quot; data-og-url=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xAQHy/hyUrtGM0PU/FXsDEjXOMkq9jVJg2px7w0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xAQHy/hyUrtGM0PU/FXsDEjXOMkq9jVJg2px7w0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - TeamNADA/NADA-iOS-ForRelease: 릴리즈를 목표로 하는 민재이준현규의 으라차차  대소동  iOS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;릴리즈를 목표로 하는 민재이준현규의 으라차차  대소동  iOS❤️&amp;zwj;  TeamNADA. Contribute to TeamNADA/NADA-iOS-ForRelease development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS</category>
      <category>containerBackground</category>
      <category>IOS</category>
      <category>iOS 17 widget error</category>
      <category>widget error</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/306</guid>
      <comments>https://gyuios.tistory.com/306#entry306comment</comments>
      <pubDate>Tue, 7 Nov 2023 23:04:41 +0900</pubDate>
    </item>
    <item>
      <title>iOS) UIScreen.main deprecated 대체하기</title>
      <link>https://gyuios.tistory.com/305</link>
      <description>&lt;p&gt;UIScreen.main 이 iOS 16 에서 Deprecated 되었습니다.&lt;/p&gt;
&lt;p&gt;대체 방안을 알아보겠습니다!&lt;/p&gt;
&lt;img width=&quot;500&quot; alt=&quot;1&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/5cddb1f3-31e8-4dd7-a188-7d98749154bd&quot;&gt;

&lt;p&gt;기기의 screen scale 을 얻기위해서 보통 다음의 방법을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// deprecated 예정
UIScreen.main.screen.scale

// windowScene 을 활용한 접근
view.window?.windowScene?.screen.scale

// UIApplication 을 활용한 접근
private let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.screen.scale&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번에는 다른 방법에 대해서 알아보려 합니다.&lt;/p&gt;
&lt;h3&gt;UITraitCollection 활용한 접근&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;UITraitCollection.current.displayScale&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드로 변경하였습니다. 이 장점은 &lt;code&gt;UIApplication&lt;/code&gt; 을 통해서 접근하는 screen 의 경우 visionOS 를 지원하지 않습니다.&lt;/p&gt;
&lt;img width=&quot;350&quot; alt=&quot;2&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/f65b99cd-555e-4652-8898-f67a2dd21329&quot;&gt;

&lt;p&gt;(출처 : &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiwindowscene/3198089-screen&quot;&gt;https://developer.apple.com/documentation/uikit/uiwindowscene/3198089-screen&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;하지만, UITraitCollection 의 displayScale 은 visionOS 를 beta 로 지원하고 있습니다.&lt;/p&gt;
&lt;img width=&quot;500&quot; alt=&quot;3&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/33218122-eb96-4c20-bc4d-ccd435e239f8&quot;&gt;

&lt;p&gt;(출처: &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uitraitcollection/1623519-displayscale&quot;&gt;https://developer.apple.com/documentation/uikit/uitraitcollection/1623519-displayscale&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;개발자 문서를 살펴보겠습니다.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“non-Retina 디스플레이스에서는 1.0을 나타내고, Retina 디스플레이스에서는 2.0 을 나타냅니다. 기본적으로 trait collection 의 display scale 은 0.0입니다.”&lt;/p&gt;
&lt;p&gt;하지만, scale 3.0 의 값을 가지는 시뮬레이터에서 확인을 해봐도 그렇고 Retina 디스플레이는 2.0 혹은 3.0 을 가집니다.&lt;/p&gt;
&lt;p&gt;아마도 visionOS 베타에서 정식으로 적용되면 개발자 문서도 내용이 바뀌지 않을까 싶습니다.&lt;/p&gt;
&lt;p&gt;바뀌게 된다면 예전 내용과 확인해보는 것도 재밌겠네요!&lt;/p&gt;</description>
      <category>iOS</category>
      <category>Deprecated</category>
      <category>IOS</category>
      <category>UIScreen.main</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/305</guid>
      <comments>https://gyuios.tistory.com/305#entry305comment</comments>
      <pubDate>Thu, 2 Nov 2023 15:23:40 +0900</pubDate>
    </item>
    <item>
      <title>iOS) 오픈 소스 라이브러리(Moya) document 에 PR 올려보기</title>
      <link>https://gyuios.tistory.com/304</link>
      <description>&lt;h3&gt;내용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Moya 리드미를 수정하여 기여해보자&lt;/li&gt;
&lt;li&gt;Moya contributing guideline 을 살펴보자&lt;/li&gt;
&lt;li&gt;github contribution guidelines 만드는 방법에 대해서 알아보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;  들어가기 전&lt;/h2&gt;
&lt;p&gt;오픈소스 라이브러리의 컨트리뷰터가 되는 것은 어려운 이야기처럼 들렸습니다.(성공한 후기가 아니기에 아직도 어렵습니다 ㅎㅎ)&lt;/p&gt;
&lt;p&gt;또한, 꾸준히 해당 관련 깃허브 활동이나 컨트리뷰터들의 커뮤니티 활동에 관심을 가져야하는 것을 느껴졌습니다. 지금까지의 이슈나 PR 이 어떻게 적혀왔고, 어떤 부분들이 리뷰를 받아왔는지 저는 거꾸로 읽어갔던 것 같습니다.&lt;/p&gt;
&lt;p&gt;PR을 올리게 되어 많은 사람들이 올바르지 않을 수 있는 나의 의견을 조회할 수 있다는 것이니까 좀 무섭기도 했지만, 마음 한켠으로는 별 상관이 없기도 했습니다.&lt;/p&gt;
&lt;p&gt;그러다가 우연한 기회로 PR을 올릴 기회가 생겼고 그 과정을 공유해볼까 합니다.&lt;/p&gt;
&lt;h2&gt;  시작&lt;/h2&gt;
&lt;p&gt;해당 리드미를 edit 하게되면 자동으로 fork 되면서 마크업으로 구성된 글을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;해당 리드미는 6년 전에 마지막으로 커밋되었네요 ㄷㄷ&lt;/p&gt;
&lt;img width=&quot;200&quot; alt=&quot;1&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/916acaf5-1a14-4be1-a04a-d51bb1719fe4&quot;&gt;

&lt;p&gt;저는 리드미 한 부분만 수정하면 되서 깃허브 웹에서 진행하였고, 만약에 코드나 여러부분을 수정해야한다면 fork 를 해서 해당 레포지토리에서 작업 후에 PR 을 생성하면 되겠습니다.&lt;/p&gt;
&lt;h2&gt;  Issue, Pull Request 생성&lt;/h2&gt;
&lt;p&gt;Issue 을 만들자 다음과 같은 템플릿이 작성되어 있었습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;&amp;lt;!--
Please let us know what version of Moya you are using, so we can better pinpoint and/or solve your issue.

Please wrap code blocks in backticks, like so:

```swift
*your code goes here*&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code will automatically get its syntax highlighted, and doesn&amp;#39;t need to be indented 4 spaces to be shown as code.&lt;/p&gt;
&lt;p&gt;When referencing a dependency manager-related issue (think CocoaPods, Carthage, SwiftPM), please add its configuration file and version to the issue.&lt;br&gt;It would be helpful to put the contents in a code block too, using &lt;code&gt;ruby for CocoaPods and&lt;/code&gt;swift for SwiftPM.&lt;/p&gt;
&lt;p&gt;Also please make sure your title describes your problem well. Questions end with a question mark.&lt;br&gt;--&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
- 이슈를 작성하자마자 bot 이 label 을 두 개 붙여주었습니다..!

&amp;lt;img width=&amp;quot;400&amp;quot; alt=&amp;quot;2&amp;quot; src=&amp;quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/2d9deb41-661d-4e86-98a9-d27d075d50f3&amp;quot;&amp;gt;

PR 을 만들자 다음과 같이 템플릿이 있었습니다.

```swift
// commit 세부사항을 적은 것이 그대로 나오고, 이하에 주석 처리된 안내사항이 적혀있습니다.
- fix example code.

&amp;lt;!--
Thank you for contributing to Moya!  

Choosing a base branch:

  master: bug fixes, non breaking API changes, documentation fixes
  development: breaking changes, features for the next version

If your pull request fixes an issue, please reference the issue.
For example, when your pull request fixes issue 10, add the following line:

Fixes #10

This will make sure that when the pull request is merged, the issue will automatically be closed.

--&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;간략하게 요약해서 어떻게 PR 을 작성할 수 있는지 정리해보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;리드미에 적힌 코드를 수정하는 것이기 때문에 &lt;strong&gt;&lt;code&gt;master&lt;/code&gt;&lt;/strong&gt; 브랜치를 base branch 로 설정하였습니다.&lt;/li&gt;
&lt;li&gt;또한, 이슈가 있다면 &lt;code&gt;**Fixes #10**&lt;/code&gt; 와 같이 줄을 추가해서 pr 이 merge 된 후에 자동으로 닫히도록 유도하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;또한, 다음과 같이 풀리퀘스트를 작성하는데 contributing guidelines 를 확인할 수 있도록 안내하고 있었습니다. (첨보네요!)&lt;/p&gt;
&lt;img width=&quot;400&quot; alt=&quot;3&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/216c4479-bc73-4ffa-9a3d-b90a71c57536&quot;&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/Moya/Moya/blob/963654cd4f82d17d7546b9d6127c29f6df091717/Contributing.md&quot;&gt;Moya Contributing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;간략하게 요약해보겠습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;**master**&lt;/code&gt; 와 &lt;code&gt;**development**&lt;/code&gt; 브랜치를 유지보수 하고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;master 브랜치는 현재 release 작업을 위한 것으로 버그 수정이나 문서 철자 수정은 이 브랜치에서 머지되어야 합니다.&lt;/li&gt;
&lt;li&gt;development 브랜치는 다음 release 작업을 위한 것으로 API 변경과 관련 문서 업데이트에 해당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pull request 가 문제에 대한 잠재적인 수정이 되는 것이 일반적이고, 거기에서 프로젝트를 돕는 것은 어려울 수 있습니다. 그래서 모든 프로젝트의 토론을 깃허브 이슈에서 하는 것을 목표로 합니다.&lt;/p&gt;
&lt;p&gt;그리고 contributor 로서 우리에게 기대하는 바가 적혀있습니다.&lt;/p&gt;
&lt;p&gt;“오픈 소스에 기여하지 못한다고 해서 나쁘게 생각하지 마세요.” 기여하는 것은 의무사항이 아니라고 언급합니다.&lt;/p&gt;
&lt;p&gt;이 외에도 공개적인 이슈에서 말할 수 없는 문제를 컨텍할 수 있도록 이메일 등을 알려주면서 문서는 마무리 됩니다.&lt;/p&gt;
&lt;h2&gt;✅ contribution guidelines&lt;/h2&gt;
&lt;p&gt;이 기능은 다음 문서를 참조해서 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors&quot;&gt;Setting guidelines for repository contributors - GitHub Docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;간략하게 요약해보겠습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;project contributors 가 좋은 작업을 할 수 있도록 &lt;strong&gt;project repository’s root, docs, or .github folder&lt;/strong&gt; 에 contribution guidelines 를 추가할 수 있습니다.&lt;/p&gt;
&lt;p&gt;누군가 pull request 또는 issue 를 만들 때, 해당 파일에 대한 링크를 볼 수 있고, repository’s &lt;code&gt;contribute&lt;/code&gt; page 에서도 볼 수 있습니다.(&lt;a href=&quot;https://github.com/Moya/moya/contribute&quot;&gt;https://github.com/Moya/moya/contribute&lt;/a&gt;) contribute 페이지 예시는 &lt;a href=&quot;https://github.com/github/docs/contribute&quot;&gt;github/docs/contribute&lt;/a&gt; 에서 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이런 가이드를 통해서 어떻게 contribute 해야하는지 전달할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이슈를 만들 때 우측하단에 &lt;strong&gt;Contributing&lt;/strong&gt; 으로 확인할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;300&quot; alt=&quot;4&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/dbe7d68b-e7d0-4517-9751-7724b0802b6f&quot;&gt;

&lt;p&gt;&lt;strong&gt;Adding a CONTRIBUTING file&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;깃허브에서 다음과 같이 파일을 추가할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/7cb87b87-d1df-4025-bb4f-e0544a34885a&quot; width =&quot;700&quot;&gt;

&lt;ul&gt;
&lt;li&gt;이때 파일명은 대소문자를 구분하지 않습니다.&lt;/li&gt;
&lt;li&gt;project repository’s root, docs, or .github 디렉토리에 추가하면 됩니다. 이를 위해서 다음과 같이 할 수 있습니다.&lt;ul&gt;
&lt;li&gt;root 에 두기 위해 &lt;strong&gt;CONTRIBUTING&lt;/strong&gt;, docs 디렉토리에 두기 위해 &lt;strong&gt;docs/CONTRIBUTING&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;두 개 이상의 CONTRIBUTING 파일이 있다면 링크에 표시되는 파일은 .github, root, docs directory 순으로 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;  생성한 PR 살펴보자&lt;/h2&gt;
&lt;p&gt;제가 수정한 변경사항은 다음과 같습니다.&lt;/p&gt;
&lt;img width=&quot;600&quot; alt=&quot;6&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/a52618f3-c838-4f74-aaaa-955fa8bf5b69&quot;&gt;

&lt;p&gt;예시를 보고 적용하던 중에 해당 코드를 따라가다 보니 error 대신 failure 가 만들어졌습니다. 이는 둘째치고, 왜 이 코드 예시가 잘못되었는지 알아보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Moya 에서 RxSwift 를 사용하여 손쉽게 서버통신의 결과물을 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이때, &lt;strong&gt;provider.rx.request()&lt;/strong&gt; 메소드를 통해 MoyaProvider 가 제공하는 &lt;code&gt;**request()**&lt;/code&gt; 의 반환형은 다음과 같이 trait 의 하나인 &lt;strong&gt;Single&lt;Response&gt; 입니다.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;400&quot; alt=&quot;7&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/841f46ed-1f2c-4340-abc5-3763b52583a0&quot;&gt;

&lt;ul&gt;
&lt;li&gt;이를 구독하기 위한 &lt;strong&gt;subscribe(_:)&lt;/strong&gt; 구현부를 살펴보게되면 Single 에서 방출하는 next, error 이벤트가  success 와 failure 로 이어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;500&quot; alt=&quot;8&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/9d98f3ee-92d0-4384-b3f7-470f571a0d99&quot;&gt;

&lt;ul&gt;
&lt;li&gt;이 외에도 Single.swift 파일에서 deprecated 된 메소드를 확인해보면 onError 를 통해 다룬 흔적을 확인할 수 있었습니다. 이를 onFailure 로 바꾼 것도 보이는데 그래서 6년 전의 커밋이 마지막이었던 리드미의 예시 코드가 .error 를 다룬 것이 아닌가 생각했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;img width=&quot;700&quot; alt=&quot;9&quot; src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/1a6aed30-702c-4687-adfb-82d6593c3057&quot;&gt;

&lt;h3&gt;그래서 다음과 같이 PR 을 올렸습니다.&lt;/h3&gt;
&lt;p&gt;2023-10-09 일자로 PR 을 올렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Moya/Moya/pull/2322&quot;&gt;https://github.com/Moya/Moya/pull/2322&lt;/a&gt;&lt;/p&gt;
&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/1d173975-ad84-4516-837a-c939ff9420b1&quot; width=&quot;600&quot;&gt;

&lt;h2&gt;  궁금한 점&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;PR 을 리뷰해주는 시간이 얼마나 걸릴지 궁금했습니다.&lt;/li&gt;
&lt;li&gt;리드미이기 때문에 다소 중요성이 갈릴 수도 있다고 생각했습니다. 이를 어떻게 Moya가 다뤄줄지 궁금했습니다.&lt;br&gt;(리드미의 양식이라던지 업데이트의 경우도 빠르게 PR 확인해주었는데 document 관련해서 머지된 PR이 2020년이 마지막이더라구요.. ㅎㅎ)&lt;/li&gt;
&lt;li&gt;bot 을 사용해서 답을 해주거나 빌드 확인을 해주는 오픈소스 라이브러리도 본 경험이 있는데 어떻게 대응해줄지 궁금했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;현재, 이슈와 PR만 올려뒀기 때문에 다음 정도만 알 수 있었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이슈에서는 bot 이 label 을 붙여주기도 하였습니다.&lt;/li&gt;
&lt;li&gt;PR에서는 &lt;strong&gt;circleci&lt;/strong&gt; 툴을 사용해서 유지관리하고 있었고, 가장 최근의 approved 된 코드리뷰는 2023년 4월이었습니다.&lt;/li&gt;
&lt;li&gt;한 달 정도가 지난 지금까지 별도의 리뷰는 받지 못했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;  느낀 점&lt;/h2&gt;
&lt;p&gt;다음에는 리드미가 아닌 코드적인 부분에서도 수정을 하는데 기여하면 좋겠다고 생각했습니다.(사실 기여만해도 좋겠다고 생각했습니다 ㅜㅜ 처음이 어려울 것 같았거든요.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;contributing guidelines&lt;/strong&gt; 를 설정할 수 있는 기능은 오픈소스 라이브러리를 만들게되면 유용한 기능이라고 느꼈습니다.&lt;/p&gt;
&lt;p&gt;대형 오픈소스 라이브러리의 guildelines 을 읽어볼 수 있는 기회가 되서 어떻게 유지 보수하는지 엿볼 수 있었습니다.&lt;/p&gt;</description>
      <category>iOS</category>
      <category>contributing</category>
      <category>contributor</category>
      <category>IOS</category>
      <category>Opensource</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/304</guid>
      <comments>https://gyuios.tistory.com/304#entry304comment</comments>
      <pubDate>Thu, 2 Nov 2023 15:12:46 +0900</pubDate>
    </item>
    <item>
      <title>iOS) UIBulrEffect 를 사용하기 + UIVisualEffectView 에 둥글기 적용하기</title>
      <link>https://gyuios.tistory.com/303</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;내용&lt;/h3&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;둥글기를 가진 bulr effect 구현하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/3d145d8c-81c3-48de-bf0b-fb6f76df1ee4&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 blur 효과를 적용해보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/bd7571c2-5dfa-45f0-b033-d951d4719221&quot; alt=&quot;2&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둥글기가&amp;hellip;.?! 이상하지만 주석에서 설명드리겠습니다.&lt;br /&gt;&lt;b&gt;해당 글은 트러블 슈팅 과정을 담았기 때문에 최종 구현을 원하시면 아래 트러블 슈팅을 읽어주시기 바랍니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://ikyle.me/blog/2022/uiblureffectstyle&quot;&gt;https://ikyle.me/blog/2022/uiblureffectstyle&lt;/a&gt;) 참고해서 에서 일반 배경일때와 검정 배경일때 적절히 비슷한 blur 효과를 찾아보았습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let blurEffect = UIBlurEffect(style: .systemMaterialLight)
// 많은 예제들이 style 로 초기화하던데 frame 으로 해보았습니다.
let visualEffectView = UIVisualEffectView(frame: tasteView.frame)
visualEffectView.effect = blurEffect

// ✅ tasteView 에 visualEffectView 를 추가해서 둥글기를 잡아보려했습니다.
// -&amp;gt; 이러면 tasteView 색상에 영향을 받음
// bgView 에 effectView 를 추가해야 blur 효과가 적용되었습니다. -&amp;gt; 이러면 둥글기가 잡히지 않음
tasteView.backgroundColor = .clear

// FIXME: - 둥글기 설정을 위해서 view 에 추가. 그러면 backgroundImageView blur 안됨.
// tasteView.addSubview(visualEffectView)
// tasteView.layer.masksToBounds = true

// FIXME: - 둥글기 적용 안됨.
if index % 2 == 0 {
// 좌측
    visualEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
} else {
// 우측
    visualEffectView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
}
visualEffectView.layer.cornerRadius = 35 / 2

bgView.addSubview(visualEffectView)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bgView 의 뷰 계층은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/ba3839d1-69f7-46db-b453-c001cf71ec0c&quot; alt=&quot;3&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과적으로,&lt;/b&gt; blur 를 bgView(image view 위 alpha 값을 가진 view)에 추가해야하는데 그렇게 되면 corner radius 가 조절되지 않았습니다.(조절할 수 있다구! 아래에서 확인해보겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maskToBounds 를 사용해서 둥글기를 조절하기 위해 UIView 를 만들어서 얹으면 해당 뷰의 색상 조절에 영향을 받아서 안됐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blur effect 를 적용해보았지만, 둥글기를 적용하기에 애를 먹었습니다 ㅜ&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  트러블 슈팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;visualEffectView 도 UIView 이기 때문에 tasteView 에 뷰를 추가해서 masksToBounds 를 true 로 만드는 것이 아닌!&lt;br /&gt;visualEffectView 의 masksToBounds 를 true 로 설정해보기로 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰가 많으니 너무 어렵게 생각했던 것 같습니다...&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;let blurEffect = UIBlurEffect(style: .systemMaterialLight)
let visualEffectView = UIVisualEffectView(frame: tasteView.frame)

tasteView.backgroundColor = .clear
visualEffectView.effect = blurEffect

if index % 2 == 0 {
// 좌측
    visualEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
} else {
// 우측
    visualEffectView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
}
visualEffectView.layer.cornerRadius = 35 / 2
// ✅
visualEffectView.layer.masksToBounds = true

bgView.addSubview(visualEffectView)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공적으로 적용되었습니다!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/TeamNADA/NADA-iOS-ForRelease/assets/69136340/f0b0a3ea-07b8-4e47-a11f-ac255b6753e8&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 둥글기를 그리는 방법 중 하나인 UIBezierPath 를 사용하는 방법으로도 구현할 수 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;let shapeLayer = CAShapeLayer()

shapeLayer.path = UIBezierPath(roundedRect: visualEffectView.bounds,
                             byRoundingCorners: [.topLeft, .bottomLeft],
                             cornerRadii: CGSize(width: 17.5, height: 17.5)).cgPath

visualEffectView.layer.mask = shapeLayer&lt;/code&gt;&lt;/pre&gt;</description>
      <category>iOS</category>
      <category>IOS</category>
      <category>UIBlurEffect</category>
      <category>UIVisualEffectView</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/303</guid>
      <comments>https://gyuios.tistory.com/303#entry303comment</comments>
      <pubDate>Thu, 2 Nov 2023 12:00:50 +0900</pubDate>
    </item>
    <item>
      <title>iOS) Adding interactivity to widgets and Live Activities(개발자문서)</title>
      <link>https://gyuios.tistory.com/302</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;interactive widget 에 대해서 알아보자&lt;/li&gt;
&lt;li&gt;관련 개발자 문서, HIG 정리해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 17과 iPad 17, macOS 14 부터 인터렉티브 위젯이 적용되었고 애플 공식 홈페이지에서 다음과 같이 소개하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/730997d9-03f4-42b1-992b-19e6cf10d449&quot; alt=&quot;1&quot; width=&quot;450&quot; /&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/5ca369ef-f485-4d52-a05d-10a8de3ad5c9&quot; alt=&quot;2&quot; width=&quot;450&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: &lt;a href=&quot;https://www.apple.com/kr/newsroom/2023/06/ipados-17-brings-new-levels-of-personalization-and-versatility-to-ipad/&quot;&gt;https://www.apple.com/kr/newsroom/2023/06/ipados-17-brings-new-levels-of-personalization-and-versatility-to-ipad/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;개발자문서를 통해서 알아보겠습니다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/adding-interactivity-to-widgets-and-live-activities&quot;&gt;Adding interactivity to widgets and Live Activities&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Overview&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 17과 iPad 17, macOS 14 부터 Widget 과 Live Activities 에 특정 앱 기능을 제공하는 버튼과 토글이 포함될 수 있습니다. 예를 들어, 미리 알림 위젯을 사용하면 사람들이 토글을 사용하여 완료된 작업을 표시할 수 있습니다. locked device 에서 버튼과 토글들이 비활성화되어 잠금 해제되지 않는 이상 시스템이 작업을 수행하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Important&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼이나 토글과의 인터렉션은 앱을 여는 것 이상의 역할을 해야 합니다. 앱을 여는 인터렉션을 제공하기 위해서 &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/Link&quot;&gt;Link&lt;/a&gt; 와 &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/View/widgetURL(_:)&quot;&gt;widgetURL(_:)&lt;/a&gt; 을 사용할 수 있습니다. &lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/linking-to-specific-app-scenes-from-your-widget-or-live-activity&quot;&gt;Linking to specific app scenes from your widget or Live Activity&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 위젯 크기에서 버튼과 토글이 포함될 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/systemsmall&quot;&gt;WidgetFamily.systemSmall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/systemmedium&quot;&gt;WidgetFamily.systemMedium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/systemlarge&quot;&gt;WidgetFamily.systemLarge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/systemextralarge&quot;&gt;WidgetFamily.systemExtraLarge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/accessorycircular&quot;&gt;WidgetFamily.accessoryCircular&lt;/a&gt; on iPhone and iPad&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/widgetfamily/accessoryrectangular&quot;&gt;WidgetFamily.accessoryRectangular&lt;/a&gt; on iPhone and iPad&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Live Activities 에는 확장된 그리고 Lock Screen presentation 에 버튼이나 토글을 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 지침은 다음을 참고할 수 있습니다. &lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/components/system-experiences/widgets&quot;&gt;Human Interface Guidelines &amp;gt; Widgets&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Related session from WWDC23 :&lt;/b&gt; &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10027&quot;&gt;Session 10027: Bring widgets to new places&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WWDC23 Bring widgets to new places&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해부터 데스크탑과 아이패드의 잠금화면, 아이폰의 스탠바이 모드, 애플워치의 스마트 스택 총 4곳으로 확장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/3776774d-834d-4728-bca0-7b7139eba065&quot; alt=&quot;3&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2023/10027&quot;&gt;https://developer.apple.com/videos/play/wwdc2023/10027&lt;/a&gt;)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HIG - Widgets&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://developer.apple.com/design/human-interface-guidelines/widgets#Adding-interactivity-to-widgets&quot;&gt;Adding interactivity to widgetsin page link&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯을 탭하거나 클릭해서 앱을 실행할 수 있습니다. iOS 17, iPadOS 17, macOS 14 부터는 위젯은 버튼과 토글을 포함하여 앱의 실행없이 추가적인 기능을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 미리 알림 위젯을 사용해 작업을 완료로 표시할 수 있고, 일일 카페인 섭취를 기록하는 앱의 위젯에서 좋아하는 음료의 카페인 섭취를 늘리는 버튼이 포함될 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/4702e92c-aad7-4eb6-ae41-353abdd5dc6c&quot; alt=&quot;4&quot; width=&quot;450&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯은 위젯의 컨텐츠와 직접적으로 관련된 작업이나 동작을 완료하기 위해 집중적이고 구체적인 앱 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼이나 토글이 아닌 영역에서 위젯과 상호작용하면 앱이 실행됩니다. 위젯 콘텐츠와 관련된 세부 정보 및 작업을 제공하는 화면에 대한 deep link 를 사용하세요. 예를 들어, medium 주식 widget 을 클릭하거나 탭했을 때, 주식 앱은 해당 종목에 대한 정보를 표시하는 페이지가 열립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 눈에 알아볼 수 있고, 깔끔하게 유지하면서 상호작용을 위한 옵션을 제공하세요. 하지만, 앱과 유사한 레이아웃을 만드는 것은 피하세요. 대상의 크기에 주의를 기울이고, 사람들이 실수로 탭하거나 클릭할 수 있는지 확인해야 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Understand the role of app intents&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯은 &lt;a href=&quot;https://developer.apple.com/documentation/AppIntents&quot;&gt;App Intents&lt;/a&gt; 프레임워크 와 SwiftUI 를 사용하여 앱과 직접적인 상호작용을 제공합니다. 예를 들어, 샘플 코드 프로젝트의 large widget 에는 버튼이 포함되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯과 워치 complications 를 만들기 위해서 프로젝트에 widget extension 을 추가해야 하고, 앱과 독립적인 프로세스에서 실행됩니다. timeline entries 를 기반으로 시스템은 view representation 을 archive 하고 이러한 representations 을 사용하여 뷰만 렌더링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timeline 매커니즘과 별도의 프로세스에서의 렌더링으로 인해 시스템은 위젯을 렌더링할 때 코드를 실행하거나 데이터 바인딩을 업데이트할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Intents 프레임워크를 사용해서 앱의 동작을 시스템에 노출하고, 필요할 때 작업을 수행할 수 있습니다.(위젯의 버튼 혹은 토글과 상호작용할 때)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Live Activities 는 콘텐츠를 업데이트하기 위해 timeline 매커니즘을 사용하지 않지만, view archiving 과 decoding 주기가 유사한 WidgetKit 과 widget extension 을 사용합니다. 그래서 아래 방법의 위젯과 동일한 방식으로 Live Activity 에 버튼과 토글을 추가할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Add an app intent that performs the action&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;widget 과 Live Activities 에 추가하는 버튼과 토글은 App Intents 프레임워크를 채택해서 시스템에 노출되는 기능을 사용합니다. 버튼이나 토글을 추가하기 전에 app intent 를 사용해서 기능을 사용할 수 있도록 설정할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위젯의 경우, AppIntent 프로토콜을 채택하는 구조체를 생성하고 app target 에 추가합니다. Live Activity 같은 경우, LiveActivityIntent protocol 을 채택합니다.&lt;/li&gt;
&lt;li&gt;프로토콜의 요구사항을 구현합니다.&lt;/li&gt;
&lt;li&gt;@Parameter 프로퍼티 래퍼를 사용하여 동작에 필요한 input parameter 를 정의하고 해당 타입이 &lt;a href=&quot;https://developer.apple.com/documentation/AppIntents/AppEntity&quot;&gt;AppEntity&lt;/a&gt; 프로토콜을 준수하는지 확인합니다. input parameter 에 값이 할당되어 있는지 확인해야 합니다. 왜냐하면, Siri 와 같은 시스템 기능에 대해 정의한 app intent 와 달리 위젯은 app intent 에 대한 매개변수를 해결하지 않습니다.&lt;/li&gt;
&lt;li&gt;프로토콜의 &lt;a href=&quot;https://developer.apple.com/documentation/AppIntents/AppIntent/perform()-2vmgc&quot;&gt;preform()&lt;/a&gt; 함수에서 위젯이 사용할 수 있도록 하려는 작업에 대한 코드를 추가합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 사람들이 클릭 혹은 터치하여 hero 의 healing boost 를 주는 버튼이 large widget 에 있다고 해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *)
struct SuperCharge: AppIntent {

    static var title: LocalizedStringResource = &quot;Emoji Ranger SuperCharger&quot;
    static var description = IntentDescription(&quot;All heroes get instant 100% health.&quot;)

    func perform() async throws -&amp;gt; some IntentResult {
        EmojiRanger.superchargeHeros()
        return .result()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LiveActivityIntent 또는 AudioPlaybackIntent 프로토콜을 채택하면 시스템은 앱 프로세스에서 app intent 를 실행합니다. app target 에 custom app intent 를 추가해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppIntent 프로토콜을 채택하는 경우, widget extension target 과 app target 에 custom app intent 를 추가해야 합니다. 이를 통해 위젯에 추가한 버튼과 토글을 사용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Implement the perform function&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 위젯의 버튼이나 토글과 상호작용하면 시스템은 앱 프로세스에서 &lt;b&gt;perform()&lt;/b&gt; 함수를 실행합니다. 이 기능은 비동기식입니다. 결과적으로 Swift concurrency 를 최대한 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perform() 함수로부터 반환되면 시스템은 timeline provider 를 사용해서 위젯의 timeline 을 다시 로드합니다. 이를 인터렉션의 결과로 위젯을 업데이트하는 기회로 활용할 수 있습니다. perform() 반환되기 전에 timeline 업데이트에 필요한 코드가 실행되는지 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 업데이트된 정보를 데이터베이스에 저장하는 동안 await 키워드를 사용하고 완료되면 perform() 에서 반환됩니다. 그런 다음 시스템은 위젯의 timeline 을 리로드하고 timeline provider 는 데이터베이스에서 업데이트된 데이터를 로드합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note&lt;/b&gt; : 버튼이나 토글은 항상 timeline 을 리로드하도록 보장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perfom() 함수는 throws 로 표시되어서 오류를 처리하여 앱, 위젯, 라이브 액티비티를 업데이트합니다. 예를 들어, 새로운 데이터를 로드할 수 없는 경우 오래된 정보를 표시한다고 사용자에게 말할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App Intents 프레임워크를 사용하여 Siri 와 Shorcuts 앱과 같은 시스템 수준의 서비스를 지원할 수 있습니다. 또한, app intents 를 사용하여 위젯을 configurable 하게 만들 수 있습니다. 위젯을 인터렉티브하게 만들면, 해당 인터렉션을 위한 시스템 서비스가 자동으로 지원됩니다. App Intents 프레임워크에 대해서 자세히 알아보려면 &lt;a href=&quot;https://developer.apple.com/documentation/AppIntents&quot;&gt;App Intents&lt;/a&gt; 와 &lt;a href=&quot;https://developer.apple.com/documentation/AppIntents/Providing-your-app-s-capabilities-to-system-services&quot;&gt;Providing your app&amp;rsquo;s capabilities to system services&lt;/a&gt;-services) 에서 볼 수 있습니다. configurable widgets 에 대해서는 &lt;a href=&quot;https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget&quot;&gt;Making a configurable widget&lt;/a&gt; 에서 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Add a button&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app intent 를 구현한 후, &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/Button/init(_:intent:)-7urde&quot;&gt;init(_:intent:)&lt;/a&gt; 와 같은 이니셜라이저를 사용해서 위젯에 버튼을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯 또는 Live Activity 만을 위해서 버튼을 구현하지 않아도 됩니다. app intent implementation 을 포함하여 위젯에 버튼을 추가하는 코드를 앱에서 재사용할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 예시는 위젯이 hero 에게 healing boost 를 주는 버튼을 포함하고 있습니다. iOS 17이 있는 확인하는 방법에 유의해야 합니다.(if #available(iOS 17.0, *) 분기처리를 말함)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct EmojiRangerWidgetEntryView: View {
    var entry: SimpleEntry

    @Environment(\.widgetFamily) var family

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemLarge:
            VStack {
                HStack(alignment: .top) {
                    AvatarView(entry.hero)
                        .foregroundColor(.white)
                    Text(entry.hero.bio)
                        .foregroundColor(.white)
                }
                .padding()
                if #available(iOS 17.0, *) {
                    HStack(alignment: .top) {
                        // ✅ AppIntent 프로토콜을 채택하는 구조체 SuperCharge.
                        Button(intent: SuperCharge()) {
                            Image(systemName: &quot;bolt.fill&quot;)
                        }
                    }
                    .tint(.white)
                    .padding()
                }
            }
            .containerBackground(for: .widget) {
                Color.gameBackgroundColor
            }
            .widgetURL(entry.hero.url)

        // Code for other widget sizes.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 추가한 후에 인터렉션할 때 데이터를 변경한 뷰를 검토합니다. 예를 들어, 버튼을 누르면 몇가지 텍스트가 위젯에 리로드 될 수 있습니다. Mac 에서 iPhone 과 상호작용하는 경우 리로드에 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯이 콘텐츠의 업데이트를 기다리고 있음을 사용자에게 알리려면 업데이트 된 데이터를 수신하는 뷰에서 &lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/View/invalidatableContent(_:)&quot;&gt;invalidatableContent(_:)&lt;/a&gt; modifier 를 추가할 수 있습니다. 변경될 수 있는 모든 뷰에 사용하지 말고 중요한 뷰에 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note :&lt;/b&gt; 버튼은 사람이 상호작용했지만 누른 상태를 유지하지 않는 것을 의미합니다. Toggle 을 사용해서 on/off 기능을 나타낼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Add a toggle&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/SwiftUI/Toggle/init(isOn:intent:label:)&quot;&gt;init(isOn:intent:label:)&lt;/a&gt; 와 같은 이니셜라이저를 사용해서 뷰에 토글을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예시는 widget 에서 toggle 을 추가하는 것을 보여줍니다. 이니셜라이저의 &lt;b&gt;isOn&lt;/b&gt; 파라미터는 Binding 값 대신 Boolean 값을 받습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;struct TodoItemView: View {
    var todo: Todo

    var body: some View {
        Toggle(isOn: todo.complete, intent: ToggleTodoIntent(todo.id)) {
            Text(todo.body)
        }
        .toggleStyle(TodoToggleStyle())
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;perform() 함수는 코드를 비동기적으로 실행하며 예를 들어, Mac 의 iPhone 위젯에서 상호작용을 수행하는 경우 시간이 걸릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Toggle 은 appearance 를 수행된 작업의 결과를 기다리지 않고 즉시 새로운 상태를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 상태를 정확하게 반영하려면 perform() 함수에서 실행한 코드 결과로 위젯을 업데이트하면 됩니다. 예를 들어, 사용자가 토글을 탭하는 경우 토글은 작업이 완료된 것으로 즉시 표시합니다. perform() 로직이 여전히 실행중이더라도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위젯이 실제로 완료된 작업만 표시하도록 하기 위해 perform() 구현을 다음처럼 시작할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위젯이 앱과 공유하는 데이터베이스에서 작업을 완료된 것으로 표시&lt;/li&gt;
&lt;li&gt;코드를 실행해서 데이터베이스를 서버와 동기화&lt;/li&gt;
&lt;li&gt;서버가 성공적인 동기화를 알려주면 위젯의 timeline 을 올바른 상태로 새로고침 합니다. Toggle 은 예측이 아닌 올바른 상태를 표시해야 합니다.&lt;/li&gt;
&lt;li&gt;오류 또는 서버에서 timeout 이 발생하는 경우 위젯이 작업이 완료되지 않음을 반영해야 합니다. 예를 들어, toggle 의 isOn 프로퍼티를 false 로 설정하고 위젯에 동기화 에러 텍스트를 보여줘야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Review interactions in iPhone widgets on Mac&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mac 의 iPhone 위젯과 관련하여 위젯에 버튼을 추가하는 경우 뷰에 invalidatableContent(_:) modifier 를 사용하여 Toggle 의 낙관적인 동작에 대해서 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iOS 17 과 macOS 14 부터 사용자는 Mac 데스크탑과 notification center 에 iPhone 위젯을 배치할 수 있습니다. 사람이 Mac 에서 인터렉션하면 시스템은 iPhone 으로 인터렉션을 보냅니다. iPhone 에서 시스템은 intent 를 수행하고 새로운 timeline 을 생성하고 업데이트된 timeline 을 Mac 의 iPhone 위젯으로 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로세스는 추가적인 시간이 좀 걸릴 수 있습니다. 따라서 업데이트된 데이터를 기다리는 뷰를 표시하고 토글이 동기화된 데이터를 반영하는지 확인하는 것이 중요합니다.&lt;/p&gt;</description>
      <category>iOS</category>
      <category>interactivity widget</category>
      <category>IOS</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/302</guid>
      <comments>https://gyuios.tistory.com/302#entry302comment</comments>
      <pubDate>Wed, 25 Oct 2023 13:34:24 +0900</pubDate>
    </item>
    <item>
      <title>iOS) UICollectionViewDataSource 대체하여 DiffableDataSource 적용해보자</title>
      <link>https://gyuios.tistory.com/301</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;내용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UICollectionViewDataSource 를 대체하여 DiffableDataSource 를 적용해보자&lt;/li&gt;
&lt;li&gt;코드 중심적으로 정리하여 추후에 사용할 때 도움이 되어보자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가기 전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 글은 다음에 정리해뒀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/153&quot;&gt;iOS) Diffable Data Source 알아보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gyuios.tistory.com/300&quot;&gt;iOS) DiffableDataSource 사용해서 collection view 를 업데이트해보자(개발자 문서)&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  적용해보자&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용할 데이터 모델을 살펴보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;public struct ReceivedTag: Codable {
    let adjective: String
    let cardTagID: Int
    // ...

    enum CodingKeys: String, CodingKey {
        case adjective, icon, noun
        case cardTagID = &quot;cardTagId&quot;
        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 데이터 모델은 위와 같습니다. 이를 diffableDataSource 에 사용하기 위해서는 &lt;b&gt;Hashable 을 채택한 데이터 모델을 요구합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 모델이 갱신될때 비교해야하기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/ddb91dc0-f099-45da-91cb-cffc1c91b026&quot; alt=&quot;1&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 하나의 컬렉션뷰 섹션을 사용할 것이기 때문에 &lt;b&gt;SectionIdentifierType&lt;/b&gt; 역할을 하기 위해서 다음과 같이 데이터 모델을 생성하였습니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;private enum Section: Hashable {
    case main
}

// ✅ 많은 예제가 다음과 같이 CaseIterable 프로토콜을 채택합니다. 이것도 되는걸까요?
private enum Section: CaseIterable {
// CaseIterable 을 채택하면 자동으로 allCasese 를 얻게되는데 이때, 조건이 associated type 이 없는 경우입니다.
// 즉, CaseIterable 을 채택할 수 있다면 associaated type 없다는 것입니다.
// enum 타입에서 associated type 이 없다면 자동으로 Hashable 을 준수하게됩니다.
    case main
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해야 Hashable 을 준수할 수 있을까요? 기존의 데이터 모델이 Hashable 을 채택하도록 Hashable 에 대해서 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(프로토콜을 채택하기 위해서 implement(구현)하는 것을 conformance(준수한다)라고 표현합니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Hashable 준수하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hashable 프로토콜은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 Strings, integers, floating-point, boolean values, sets 는 Hashable 을 채택합니다. optionals 이라던지 arrays 와 같은 타입들의 경우는 해당 타입이 hashable 하다면 자동으로 hashable 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 타입이 Hashable 을 다음과 같이 채택할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;associated value 가 없는 enum 타입은 자동으로 Hashable 를 준수&lt;/li&gt;
&lt;li&gt;hash(into:) 메소드를 구현하여 커스텀 타입이 Hashable 준수하도록 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Hashable 프로토콜은 Equatable 프로토콜을 상속하므로 해당 프로토콜도 준수해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hashable 을 준수하기 위해서 구현해야하는 hash(into:) 메소드를 컴파일러가 자동으로 제공하는 경우도 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구조체의 경우 저장된 프로퍼티들이 모두 Hashable.&lt;/li&gt;
&lt;li&gt;Hashable 한 associated value 를 가진 enum 타입.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처: &lt;a href=&quot;https://developer.apple.com/documentation/swift/hashable&quot;&gt;https://developer.apple.com/documentation/swift/hashable&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스인 경우에 Hashable 을 준수하도록 해보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class TagDataModel: Codable, Hashable {
    let name: String
    let id: Int

    public func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(id)
    }
    // hash(into:) 만 구현하면 Equatable 프로토콜을 채택하라는 에러 메시지가 뜸.    

    // Equatable 을 채택하는 Hashable 을 채택하기 때문에 구현해야 함.    
    public static func == (lhs: TagDataModel, rhs: TagDataModel) -&amp;gt; Bool {
        return lhs.name == rhs.name &amp;amp;&amp;amp; lhs.id == rhs.id
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;다시 돌아와서 데이터 모델들을 수정해보겠습니다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장 프로퍼티들이 Hashable 한 구조체의 경우 Hashable 을 준수하기 위해서 구현해야하는 바가 없기 때문에 다음과 같이 Hashable 프로토콜을 채택하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public struct ReceivedTag: Codable, Hashable {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 데이터 모델 안에 기본 자료형이 아닌 다른 데이터 모델들이 들어있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TagDataModel 이 Hashable 하지 않기 때문에 ReceivedTag 데이터 모델은 Hashable 을 준수할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/46f338a8-0dff-428d-9afa-1b158553da0e&quot; alt=&quot;2&quot; width=&quot;600&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 다음과 같이 구현해줘야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/dc1007d2-31a8-45e1-9a13-9cab9facd0bf&quot; alt=&quot;3&quot; width=&quot;300&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hashable 프로토콜을 채택하게 된 데이터 모델을 사용하여 다음과 같이 diffableDataSource 변수를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private var diffableDataSource: UICollectionViewDiffableDataSource&amp;lt;Section, ReceivedTag&amp;gt;?   &lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data source 를 대체하여 적용해보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// viewController.swift

    private var diffableDataSource: UICollectionViewDiffableDataSource&amp;lt;Section, ReceivedTag&amp;gt;?   

    // ...

    private func setDelegate() {
        collectionView.delegate = self
//        collectionView.dataSource = self
        collectionView.register(TagCVC.self, forCellWithReuseIdentifier: &quot;TagCVC&quot;)

        diffableDataSource = UICollectionViewDiffableDataSource&amp;lt;Section, ReceivedTag&amp;gt;(collectionView: collectionView) { [weak self] collectionView, indexPath, itemIdentifier in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: &quot;TagCVC&quot;, for: indexPath) as? TagCVC else { return UICollectionViewCell() }

            // 아래와 같이 동일한 동작으로 사용할 수 있습니다.
            cell.initCell(itemIdentifier.name)
            cell.initCell(receivedTags[indexPath.item].name)

            return cell
        }

        collectionView.dataSource = diffableDataSource
    }

// 초기 구성 후, 새로운 data source가 필요하다면 새로운 collection view 와 data source 를 만들어야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 데이터를 로드했을 때 컬렉션 뷰를 세팅해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬렉션 뷰에 데이터를 보여주기 위해서는 apply&amp;hellip; 로 시작하는 4개의 메소드를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/d58f0f5b-c04d-4d03-9cd3-1bdad84703dd&quot; alt=&quot;4&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3804469-applysnapshotusingreloaddata&quot;&gt;applySnapshotUsingReloadData(_:)&lt;/a&gt;&lt;/b&gt; : iOS 15 +. 차이점을 계산하거나 변경 사항에 애니메이션을 적용하지 않고, 스냅샷의 상태만 반영하도록 UI 를 재설정하는 메소드입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3804470-applysnapshotusingreloaddata&quot;&gt;applySnapshotUsingReloadData(_:completion:)&lt;/a&gt;&lt;/b&gt; : iOS 15 +. applySnapshotUsingReloadData(_:) 동일하고, 애니메이션 적용 후 completion handler 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3795617-apply&quot;&gt;apply(_:animatingDifferences:)&lt;/a&gt;&lt;/b&gt; : iOS 15 +. UI 를 업데이트하고, 선택적으로 변경사항에 애니메이션을 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3375795-apply&quot;&gt;apply(_:animatingDifferences:completion:)&lt;/a&gt;&lt;/b&gt; : iOS 13 +. apply(_:animatingDifferences:) 동일하고, 애니메이션 적용 후 completion handler 실행.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10252/&quot;&gt;WWDC 2021 &amp;gt; Make blazing fast lists and collection views&lt;/a&gt;&amp;nbsp;&lt;/b&gt;을 살펴보면 iOS 13 이상 사용 가능한 apply(_:animatingDifferences:completion:) 과 iOS 15이상 사용 가능한 apply(_:animatingDifferences:) 의 동작 차이에 대해서 설명해줍니다.(2분40초부터)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS 15 이전에는 animatingDifferences 파라미터가 false 인 경우에는 내부적으로 reloadData() 바뀌어서 동작하였습니다.(컬렉션 뷰의 모든 셀을 재생성해야 했기 때문에 퍼포먼스가 좋지 않았다고 합니다.)&lt;/li&gt;
&lt;li&gt;iOS 15 부터는 animatingDifferences 파라미터가 false 인 경우에도 차이만 적용하고 다른 extra work 를 하지 않도록 동작한다고 합니다.&lt;/li&gt;
&lt;li&gt;그리고 해당 completion 파라미터는 nil 기본값을 가지고 있기 때문에 apply(_: animatingDifferences:) 를 사용하게되면, iOS 15 이전에는 apply(_:animatingDifferences:completion:) 메소드가 호출될 것이고 iOS 15 부터는 apply(_:animatingDifferences:) 가 호출될 것입니다. 즉, 코드 변화 없이 적용될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;초기 데이터 로드에서 applySnapshotUsingReloadData(_:) 메소드를 사용하여 변경사항 비교 없이 스냅샷의 상태를 반영하고, UI 업데이트를 위해서 apply(_:animatingDifferences:) 메소드를 호출할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 저는 초기 데이터 로드 과정없이 서버 통신 후의 데이터 로드 과정을 가져갔기 때문에 다음과 같이 apply(_:animatingDifferences:) 메소드를 사용하여 적용하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;snapshot 을 갱신하겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private func setCollectionView() {
    var snapshot = NSDiffableDataSourceSnapshot&amp;lt;Section, ReceivedTag&amp;gt;()

    snapshot.appendSections([.main])

    if let receivedTags {
        snapshot.appendItems(receivedTags)
    }

    diffableDataSource?.apply(snapshot, animatingDifferences: true)
}

// 서버 통신 후 데이터를 반영하는데 사용.
RxAlamofire.requestData(.get, url)
    .map { $1 }
    .bind(with: self, onNext: { owner, data in
        owner.receivedTags = data
        DispatchQueue.main.async {
         // owner.collectionView.reloadData()
            owner.setCollectionView()
        }
    })
    .disposed(by: disposedBag)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션이 생겨 collection view 를 reloadData 하여 갑자기 변하는 것보다 자연스러워졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/cb1a2304-5d83-4117-aba6-5d1e02ce6971&quot; width=&quot;250&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  느낀 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UICollectionViewDataSource 에서 필수록 구현해야했던 두 메소드를 구현하지않아도 되었고, 애니메이션을 위해서 별도의 코드없이도 자연스럽게 적용할 수 있었습니다. 채택하기 위해 요구하는 메소드를 구현해야하는 과정에서 diffable data source 객체를 만들어서 할당하는 과정이 새로워서 직관적이게 느껴졌던 것 같습니다.&lt;/li&gt;
&lt;li&gt;또한, UICollectionViewDataSource 를 사용할때와 달리 해싱으로 item 에 접근하기 때문에 셀을 재구성하거나 새롭게 교체하거나 ID 를 활용하는 성능을 향상시킬 수 있는 여지가 주어지는 점도 장점으로 느껴졌습니다.&lt;/li&gt;
&lt;li&gt;반면, 복잡한 프로세스는 아니지만 identifier 를 사용하기 위해서 데이터 타입은 반드시 Hashable 프로토콜을 채택해야만 하는 점이 있었습니다.&lt;/li&gt;
&lt;li&gt;아래와 같이 섹션 수가 잘못되어 앱이 종료되는 경우를 방지할 수 있었습니다. 데이터의 변경 상황을 동기화하기 위해서 reloadData() 를 사용하게 됩니다. 이때 시간이 지남에 따라 UI 와 DataSource 역할을 하는 controller 가 own version of the truth 를 가지게 됩니다. 이 centralize 된 truth 가 없기 때문에 서로의 truth 가 맞지 않아 아래의 에러가 나는 것을 방지할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/977aaaae-9bd4-45d5-87a2-24af6e176022&quot; width=&quot;700&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(출처 : &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2019/220&quot;&gt;WWDC 2019 &amp;gt; Advances in UI Data Source&lt;/a&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;diffable data source 가 저장하는 section 과 item identifiers 는 변하지 않고 안정적인 identifiers 입니다. 이는 UICollectionViewDataSource 의 안정적이지 않은 indices 와 index path 와 대비됨을 경험하였습니다.&lt;/li&gt;
&lt;li&gt;identifiers 를 가진 diffable data source 는 컬렉션 뷰 내의 위치(indices, index path)에 대한 지식 없이 section 과 item 을 참조할 수 있었습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>iOS</category>
      <category>diffable data soure</category>
      <category>IOS</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/301</guid>
      <comments>https://gyuios.tistory.com/301#entry301comment</comments>
      <pubDate>Tue, 17 Oct 2023 13:17:34 +0900</pubDate>
    </item>
    <item>
      <title>iOS) DiffableDataSource 사용해서 collection view 를 업데이트해보자(개발자 문서)</title>
      <link>https://gyuios.tistory.com/300</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 문서를 참고하여 단계별로 알아보고 진행하여 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/updating_collection_views_using_diffable_data_sources&quot;&gt;Updating Collection Views Using Diffable Data Sources | Apple Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Overview&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 UICollectionViewDataSource 를 채택하여 컬렉션 뷰를 채웁니다. 복잡한 데이터 추가, 삭제 및 이동 핸들링 과정을 피하기 위해서 UICollectionViewDiffalbeDataSource 객체를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;diffable data source 가 저장하는 section 과 item identifiers 는 변하지 않고 안정적인 identifiers 입니다. 이는 UICollectionViewDataSource 의 안정적이지 않은 indices 와 index path 와 대비됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identifiers 를 가진 diffable data source 는 컬렉션 뷰 내의 위치(indices, index path)에 대한 지식 없이 section 과 item 을 참조할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identifier 를 사용하기 위해서 데이터 타입은 반드시 Hashable 프로토콜을 채택해야만 합니다. Hashing 을 사용해서 Set, Dictionary 및 snapshots 같은 데이터 컬렉션에서 값을 Key 를 사용하여 빠르고 효율적으로 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identifiers 는 hashalbe 하고 equatable 하기 때문에 현재 스냅샷과 다른 스냅샷 간의 차이점을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Important&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효율성을 높이기 위해서 해시 충돌이 발생하지 않도록 요구합니다. 가끔 일어나는 불가피한 충돌은 괜찮지만 성능 저하를 우려하여 최소한으로 유지하도록 요구합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ Define the Diffable Data Source&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록을 표시하기 전에 diffable data source 를 저장하기 위해서 인스턴스 변수를 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;private var recipeListDataSource: UICollectionViewDiffableDataSource&amp;lt;RecipeListSection, Recipe.ID&amp;gt;!
// Identifier type 으로 RecipeListSection 을 item identifier type 으로 Recipe.ID 를 사용.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음과 같이 단 하나의 섹션을 가지는 enum 타입을 정의하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;private enum RecipeListSection: Int {
    case main
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Recipe 구조체는 Identifiable 프로토콜을 채택하여 id 값을 포함하고 있습니다. Recipe.ID 는 Hashable 하기 때문에 identifier type 으로 설정될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;struct Recipe: Identifiable, Codable {
    var id: Int
    var title: String
    var prepTime: Int   // In seconds.
    var cookTime: Int   // In seconds.
    var servings: String
    var ingredients: String
    var directions: String
    var isFavorite: Bool
    var collections: [String]
    fileprivate var addedOn: Date? = Date()
    fileprivate var imageNames: [String]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 스냅샷에 Recipe 전체 데이터가 아닌 Recipe.ID 값만 포함되었습니다. 이러한 접근 방식은 컬렉션 뷰에 레시피를 표시할 때 최대 성능을 낼 수 있도록 diffable data source 를 최적화할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Configure the Diffable Data Source&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UICollectionViewDiffableDataSource 인스턴스를 만들고 cell provider 를 설정합니다. 이는 컬렉션 뷰를 윈한 cell 을 구성하고 반환하는 클로저입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 CellRegistration 을 만들어서 진행한 예시 코드입니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private func configureDataSource() {
    // Create a cell registration that the diffable data source will use.
    let recipeCellRegistration = UICollectionView.CellRegistration&amp;lt;UICollectionViewListCell, Recipe&amp;gt; { cell, indexPath, recipe in
        var contentConfiguration = UIListContentConfiguration.subtitleCell()
        // ...
        cell.contentConfiguration = contentConfiguration
        // ...
    }

    // Create the diffable data source and its cell provider.
    recipeListDataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, identifier -&amp;gt; UICollectionViewCell in
        // `identifier` is an instance of `Recipe.ID`. Use it to
        // retrieve the recipe from the backing data store.
        let recipe = dataStore.recipe(with: identifier)!
        return collectionView.dequeueConfiguredReusableCell(using: recipeCellRegistration, for: indexPath, item: recipe)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UICollectionViewCell 을 만들어서 진행하는 경우, 저는 아래와 같이 진행하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// viewController.swift

    private var diffableDataSource: UICollectionViewDiffableDataSource&amp;lt;Section, ReceivedTag&amp;gt;?   

    // ...

    private func configureDataSource() {
//        collectionView.dataSource = self
        collectionView.register(TagCVC.self, forCellWithReuseIdentifier: &quot;TagCVC&quot;)

        diffableDataSource = UICollectionViewDiffableDataSource&amp;lt;Section, ReceivedTag&amp;gt;(collectionView: collectionView) { [weak self] collectionView, indexPath, itemIdentifier in
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: &quot;TagCVC&quot;, for: indexPath) as? TagCVC else { return UICollectionViewCell() }

            // 아래와 같이 동일한 동작으로 사용할 수 있습니다.
            cell.initCell(itemIdentifier.name)
            cell.initCell(receivedTags[indexPath.item].name)

            return cell
        }

        collectionView.dataSource = diffableDataSource
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ Load the Diffable Data Source with Identifiers&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data source 에 데이터의 초기 로드를 수행하여 컬렉션 뷰를 채웁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메소드는 &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot&quot;&gt;NSDiffableDataSourceSnapshot&lt;/a&gt; 인스턴스를 생성합니다. &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3804469-applysnapshotusingreloaddata&quot;&gt;applySnapshotUsingReloadData(_:)&lt;/a&gt; 메소드를 호출하여 snapshot 을 data source 에 적용하고 차이점을 계산하거나 변경사항에 애니메이션 없이 컬렉션 뷰를 재설정 합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private func loadRecipeData() {
    // Retrieve the list of recipe identifiers determined based on a
    // selected sidebar item such as All Recipes or Favorites.
    guard let recipeIds = recipeSplitViewController.selectedRecipes?.recipeIds()
    else { return }

    // Update the collection view by adding the recipe identifiers to
    // a new snapshot, and apply the snapshot to the diffable data source.
    var snapshot = NSDiffableDataSourceSnapshot&amp;lt;RecipeListSection, Recipe.ID&amp;gt;()
    snapshot.appendSections([.main])
    snapshot.appendItems(recipeIds, toSection: .main)
    recipeListDataSource.applySnapshotUsingReloadData(snapshot)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ Insert, Delete, and Move Items&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 데이터의 초기 로드에 대해 살펴봤다면 이번에는 데이터가 더해지거나 삭제되거나 순서를 변경에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data collection 에 대한 변경 사항을 처리하기 위해 앱은 현재 상태를 나타내는 새 snapshot 을 생성하고 diffable data source 에 적용합니다. 이를 현재 snapshot 과 변경사항을 비교해서 필요한 삽입, 삭제, 이동을 수행하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data collection 의 변경을 모니터링하지 않지만, 새로운 snapshot 을 적용하여 data 의 변화를 추적하는 것은 앱의 몫입니다. 이때, 데이터의 변경사항을 NotificationCenter 혹은 Combine 을 사용해서 앱에 알릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/uikit/uicollectionviewdiffabledatasource/3375795-apply&quot;&gt;apply(_:animatingDifferences:)&lt;/a&gt; 메소드는 표시되는 데이터를 완전히 재설정하는 것이 아닌 incremental updates 를 수행합니다. 그리고 animatingDifferences 를 true 로 설정하여 애니메이션을 적용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var snapshot = NSDiffableDataSourceSnapshot&amp;lt;RecipeListSection, Recipe.ID&amp;gt;()
snapshot.appendSections([.main])
snapshot.appendItems(selectedRecipeIds, toSection: .main)
recipeListDataSource.apply(snapshot, animatingDifferences: true)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ Update Existing Items&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 아이템의 프로퍼티를 처리하기 위해 앱은 diffable data source 에서 현재 snapshot 을 가져와 &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot/3804468-reconfigureitems&quot;&gt;reconfigureItems(_:)&lt;/a&gt; 또는 &lt;a href=&quot;https://developer.apple.com/documentation/uikit/nsdiffabledatasourcesnapshot/3375783-reloaditems&quot;&gt;reloadItems(_:)&lt;/a&gt; 을 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때에도 역시나 diffable data source 가 추적하는 것이 아닌 앱이 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드에서는 단일 레시피의 데이터 변경을 구현하고 있습니다. 하나의 레시피만 변경되었으므로 컬렉션 뷰의 전체 레시피 목록을 업데이트할 필요가 없습니다. 변경된 레시피를 표시하는 셀만 업데이트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 코드는 notification 이 제공하는 레시피 ID 를 사용해 reconfigureItems(_:) 메소드를 통해 특정 레시피를 업데이트 합니다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;@objc
private func recipeDidChange(_ notification: Notification) {
    guard
        // Get `recipeId` from from the `userInfo` dictionary.
        let userInfo = notification.userInfo,
        let recipeId = userInfo[NotificationKeys.recipeId] as? Recipe.ID,
        // Confirm that the data source contains the recipe.
        recipeListDataSource.indexPath(for: recipeId) != nil
    else { return }

    // Get the diffable data source's current snapshot.
    var snapshot = recipeListDataSource.snapshot()
    // Update the recipe's data displayed in the collection view.
    snapshot.reconfigureItems([recipeId])
    recipeListDataSource.apply(snapshot, animatingDifferences: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;reloadItems(_:) vs reconfigureItems(_:)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reconfigureItems(_:) 메소드는 기존 셀을 재구성하기 때문에 prepareForReuse 를 호출하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;existing cell 을 새로운 cell 로 바꾸지 않고 내용을 업데이트하려면 reladItems(_:) 대신 reconfigureItems(_:) 메소드를 사용합니다. 즉, 성능을 위해서 새로운 cell 로 교체(다른 타입의 셀 반환)해야하는 필요가 없다면 reload 하는 대신 reconfigure 를 선택할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6️⃣ &lt;b&gt;Populate Snapshots with Lightweight Data Structures&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;identifiers 를 저장하는 또 다른 접근 방식에는 diffable data source 와 snapshot 을 lightweight 의 데이터 구조체들로 populate(채우는) 것도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 접근 방식은 빠른 프로토타이핑이나 정적인 프로퍼티를 가진 items 의 경우 편리하고 적합할 수 있지만, 장단점을 가집니다. 예를 들어, 변할 수 있는 구조체의 모든 프로퍼티가 Hashable, Equatable 이 구현되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음의 예시 코드는 lightweight 데이터 구조체를 사용하여 사이드바를 정의합니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;private struct SidebarItem: Hashable {
    let title: String
    let type: SidebarItemType

    enum SidebarItemType {
        case standard, collection, expandableHeader
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티의 값이 변경되지 않으므로 SidebarItem 구조체로 snapshot 을 채우는 것이 가능한 사례입니다.&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;private func createSnapshotOfStandardItems() -&amp;gt; NSDiffableDataSourceSectionSnapshot&amp;lt;SidebarItem&amp;gt; {
    let items = [
        SidebarItem(title: StandardSidebarItem.all.rawValue, type: .standard),
        SidebarItem(title: StandardSidebarItem.favorites.rawValue, type: .standard),
        SidebarItem(title: StandardSidebarItem.recents.rawValue, type: .standard)
    ]
    return createSidebarItemSnapshot(.standardItems, items: items)
}

// StandardSidebarItem
private enum StandardSidebarItem: String, CaseIterable {
    case all = &quot;All Recipes&quot;
    case favorites = &quot;Favorites&quot;
    case recents = &quot;Recents&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/hyun99999/algorithm-Swift/assets/69136340/c3da4792-9f47-4f16-bcc9-46ac762ba7d4&quot; alt=&quot;스크린샷 2023-10-17 오전 10 57 19&quot; width=&quot;400&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 접근 방식의 단점은 diffable data source 가 더이상 ID 를 추적할 수 없는 것입니다. existing item 이 변경될 때 diffable data source 는 이전 항목을 삭제하고 새 항목 추가로 간주합니다. 결과적으로 컬렉션 뷰는 item 과 관련된 중요한 상태를 잃어버립니다. 예를 들어, diffable data source 관점에서 앱이 해당 item 를 삭제하고 대신 새 item 을 추가하기 때문에 선택한 item 이 선택 취소됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, animatingDifferences 가 true 인 경우에 snapshot 을 apply 하면 모든 변화는 애니메이션 과정을 필요로 합니다. 이는 성능에 해롭고, 셀 내에서 애니메이션을 포함한 UI상태가 손실될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, identifiers 를 사용해야 하기 때문에 reconfigureItems(_:) 또는 reloadItems(_:) 메서드를 사용하는 것이 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;existing items 을 업데이트하는 유일한 메커니즘은 새로운 데이터 모델을 포함하는 새로운 snapshot 을 적용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;items 변경되지 않거나 item 의 identifier 가 중요하지 않은 경우에만 해당 접근 방식을 사용하세요.&lt;/p&gt;</description>
      <category>iOS</category>
      <category>Diffable Data Source</category>
      <category>IOS</category>
      <author>hyun99999</author>
      <guid isPermaLink="true">https://gyuios.tistory.com/300</guid>
      <comments>https://gyuios.tistory.com/300#entry300comment</comments>
      <pubDate>Tue, 17 Oct 2023 12:23:53 +0900</pubDate>
    </item>
  </channel>
</rss>