<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Uncategorized » MszPro・株式会社Smartソフト</title>
	<atom:link href="https://mszpro.com/category/uncategorized/feed" rel="self" type="application/rss+xml" />
	<link>https://mszpro.com</link>
	<description>iOS VisionOS SwiftUI Programming Blog. Dream it, Chase it, Code it.</description>
	<lastBuildDate>Mon, 03 Feb 2025 05:02:16 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>

<image>
	<url>https://static-assets.mszpro.com/2024/12/cropped-Unknown-32x32.webp</url>
	<title>Uncategorized » MszPro・株式会社Smartソフト</title>
	<link>https://mszpro.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Tokyo Disney 2-day travel guide &#8211; DisneySea &#038; Land 2025 February &#8211; 15 Attractions</title>
		<link>https://mszpro.com/tokyo-disney-2025</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 03 Feb 2025 05:02:16 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=631</guid>

					<description><![CDATA[<p>I went to Tokyo Disney for 2 days, which includes the Tokyo Disney Sea on day one and Tokyo Island on day 2. I played a total of 15 attractions &#8211; 7 at DisneySea and 8 at the Disneyland . For me, Disney is like a recharge station for my spirit, it fills you with [&#8230;]</p>
<p>The post <a href="https://mszpro.com/tokyo-disney-2025">Tokyo Disney 2-day travel guide – DisneySea & Land 2025 February – 15 Attractions</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I went to Tokyo Disney for 2 days, which includes the Tokyo Disney Sea on day one and Tokyo Island on day 2. I played a total of 15 attractions &#8211; 7 at DisneySea and 8 at the Disneyland .</p>



<p>For me, Disney is like a recharge station for my spirit, it fills you with imaginations and brightness to your heart. I hope you feel the same!</p>



<p>If you arrived to Tokyo via Shinkansen, you can take the Keiyo train line toward the Maihama station.</p>



<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d921.0466522201375!2d139.88326941502407!3d35.6360212632416!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60187d118dd7c153%3A0x11bed1a01ae12fc3!2sMaihama%20Station!5e0!3m2!1sen!2sjp!4v1738496101910!5m2!1sen!2sjp" width="600" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="586" height="1024" src="https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-586x1024.jpeg" alt="" class="wp-image-632" srcset="https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-172x300.jpeg 172w, https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-586x1024.jpeg 586w, https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-768x1342.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-879x1536.jpeg 879w, https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1-1172x2048.jpeg 1172w, https://static-assets.mszpro.com/2025/02/IMG_8AE8BB0A3E45-1.jpeg 1320w" sizes="(max-width: 586px) 100vw, 586px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p>The Disney Resort Line has 4 stations.</p>



<p>When you get off from the JR Keiyo line Maihama station (京葉線 舞浜駅), you are at the Resort Gateway Station.</p>



<p>The train goes in a circle, from Resort Gateway Station (JR Train line) to Tokyo Disneyland Staton, to Bayside Station (hotels), and to the Tokyo DisneySea Station (entrance to Disney Sea).</p>
</div>
</div>



<p>You can use Suica or Icoca for the train, however, if you are staying for more than a day, you can purchase a 2-day pass:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full"><img decoding="async" src="https://static-assets.mszpro.com/2025/02/IMG_4663.heic" alt="" class="wp-image-634"/></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p>A 2-day pass allows you to take trains of the Tokyo Disney Resort line at any time.</p>



<p>You can also get a character image there.</p>



<p>There is a key: different ticketing machines issue a different character design. Please pay attention to the image shown on machine screen before making the purchase.</p>
</div>
</div>



<p>You should 1000% download and use the Disney app, since you will be able to get Standby pass and Disney Premier Access pass to access attractions and shows quickly, and to use mobile orders at restaurants and skip the line (believe me the lines are sometimes long). </p>



<p>I was able to play more than 10 attractions within 2 days by using the Disney Premier Access (DPA), which is a few taps away in the app.</p>



<p>Here is what I played at the park:</p>



<h2 class="wp-block-heading">Day one (7 attractions) at DisneySea</h2>



<p>Arrived at 1PM at Tokyo Disney Sea:</p>



<ul class="wp-block-list">
<li>20,000 Leagues Under the Sea</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️🌟 Cool</p>



<p>You will go into a submarine and it will go under water to explore. </p>



<p>I did not have to purchase a DPA and the wait is only 10 minutes.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img decoding="async" width="600" height="1024" data-id="636" src="https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-600x1024.jpeg" alt="" class="wp-image-636" srcset="https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-176x300.jpeg 176w, https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-600x1024.jpeg 600w, https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-768x1311.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-900x1536.jpeg 900w, https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1-1199x2048.jpeg 1199w, https://static-assets.mszpro.com/2025/02/IMG_531608F7F9F8-1.jpeg 1320w" sizes="(max-width: 600px) 100vw, 600px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" width="583" height="1024" data-id="635" src="https://static-assets.mszpro.com/2025/02/View-recent-photos-583x1024.jpeg" alt="" class="wp-image-635" srcset="https://static-assets.mszpro.com/2025/02/View-recent-photos-171x300.jpeg 171w, https://static-assets.mszpro.com/2025/02/View-recent-photos-583x1024.jpeg 583w, https://static-assets.mszpro.com/2025/02/View-recent-photos-768x1348.jpeg 768w, https://static-assets.mszpro.com/2025/02/View-recent-photos-875x1536.jpeg 875w, https://static-assets.mszpro.com/2025/02/View-recent-photos-1167x2048.jpeg 1167w, https://static-assets.mszpro.com/2025/02/View-recent-photos.jpeg 1320w" sizes="(max-width: 583px) 100vw, 583px" /></figure>
</figure>



<ul class="wp-block-list">
<li>Journey to the Center of the Earth (DPA)</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<p>This is super exciting! You will take an excavator and explore within a volcano. At the end of the ride, it will go on roller coaster mode!</p>



<p>Here is the official Disney video:</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】センター・オブ・ジ・アース / Journey to the Center of the Earth | 東京ディズニーシー/Tokyo DisneySea" width="500" height="281" src="https://www.youtube.com/embed/KjQOMyit-sM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="581" height="1024" src="https://static-assets.mszpro.com/2025/02/View-recent-photos-2-581x1024.jpeg" alt="" class="wp-image-637" srcset="https://static-assets.mszpro.com/2025/02/View-recent-photos-2-170x300.jpeg 170w, https://static-assets.mszpro.com/2025/02/View-recent-photos-2-581x1024.jpeg 581w, https://static-assets.mszpro.com/2025/02/View-recent-photos-2-768x1353.jpeg 768w, https://static-assets.mszpro.com/2025/02/View-recent-photos-2-872x1536.jpeg 872w, https://static-assets.mszpro.com/2025/02/View-recent-photos-2-1163x2048.jpeg 1163w, https://static-assets.mszpro.com/2025/02/View-recent-photos-2.jpeg 1320w" sizes="auto, (max-width: 581px) 100vw, 581px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p>You also get to see captain Nemo&#8217;s lab before getting on the ride.</p>
</div>
</div>



<p>The wait for this is too long! But I purchased the Disney Premier Access within the app and got on the ride within less than 10 minutes.</p>



<p>Here is how to purchase it, it is around $10:</p>



<ul class="wp-block-list">
<li>Register your ticket ahead of time in the Tokyo Disney Resort app</li>



<li>Use the QR code within the app to enter the park</li>



<li>Make sure the app has GPS access</li>



<li>Tap the stars icon at the bottom drawer menu</li>



<li>Tap Disney Premier Access and continue from there</li>



<li>Note that after purchasing one of the DPA, you can only purchase the next one either after you complete the current one, or after an hour.</li>
</ul>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】ディズニー・プレミアアクセス / Disney Premier Access | 東京ディズニーリゾート/TokyoDisneyResort" width="500" height="281" src="https://www.youtube.com/embed/6f_JV7EsKs4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>Then, after this ride, I went for the relaxing route of taking a boat in Venice:</p>



<ul class="wp-block-list">
<li>Venetian Gondolas</li>
</ul>



<p>You can teleport to Venice quickly and enjoy the boat ride. Don&#8217;t forget to say Ciao when you pass another boat!</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】ヴェネツィアン・ゴンドラ / Venetian Gondolas | 東京ディズニーシー/Tokyo DisneySea" width="500" height="281" src="https://www.youtube.com/embed/YrXPuDQBKQQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<ul class="wp-block-list">
<li>Checked out the US Steamship</li>
</ul>



<p>There is a large ship with multiple decks. You can board it and feel free to check it up:</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img decoding="async" data-id="641" src="https://static-assets.mszpro.com/2025/02/IMG_4751.heic" alt="" class="wp-image-641"/></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="582" height="1024" data-id="639" src="https://static-assets.mszpro.com/2025/02/View-recent-photos-3-582x1024.jpeg" alt="" class="wp-image-639" srcset="https://static-assets.mszpro.com/2025/02/View-recent-photos-3-170x300.jpeg 170w, https://static-assets.mszpro.com/2025/02/View-recent-photos-3-582x1024.jpeg 582w, https://static-assets.mszpro.com/2025/02/View-recent-photos-3-768x1352.jpeg 768w, https://static-assets.mszpro.com/2025/02/View-recent-photos-3-873x1536.jpeg 873w, https://static-assets.mszpro.com/2025/02/View-recent-photos-3-1164x2048.jpeg 1164w, https://static-assets.mszpro.com/2025/02/View-recent-photos-3.jpeg 1320w" sizes="auto, (max-width: 582px) 100vw, 582px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="586" height="1024" data-id="640" src="https://static-assets.mszpro.com/2025/02/View-recent-photos-4-586x1024.jpeg" alt="" class="wp-image-640" srcset="https://static-assets.mszpro.com/2025/02/View-recent-photos-4-172x300.jpeg 172w, https://static-assets.mszpro.com/2025/02/View-recent-photos-4-586x1024.jpeg 586w, https://static-assets.mszpro.com/2025/02/View-recent-photos-4-768x1342.jpeg 768w, https://static-assets.mszpro.com/2025/02/View-recent-photos-4-879x1536.jpeg 879w, https://static-assets.mszpro.com/2025/02/View-recent-photos-4-1172x2048.jpeg 1172w, https://static-assets.mszpro.com/2025/02/View-recent-photos-4.jpeg 1320w" sizes="auto, (max-width: 586px) 100vw, 586px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="646" src="https://static-assets.mszpro.com/2025/02/IMG_4795.heic" alt="" class="wp-image-646"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="647" src="https://static-assets.mszpro.com/2025/02/IMG_4796.heic" alt="" class="wp-image-647"/></figure>
</figure>



<p>Pro tip: the ship is much more beautiful at night! Don&#8217;t forget to take a picture!</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="778" height="1024" src="https://static-assets.mszpro.com/2025/02/View-recent-photos-5-778x1024.jpeg" alt="" class="wp-image-642" srcset="https://static-assets.mszpro.com/2025/02/View-recent-photos-5-228x300.jpeg 228w, https://static-assets.mszpro.com/2025/02/View-recent-photos-5-778x1024.jpeg 778w, https://static-assets.mszpro.com/2025/02/View-recent-photos-5-768x1011.jpeg 768w, https://static-assets.mszpro.com/2025/02/View-recent-photos-5-1167x1536.jpeg 1167w, https://static-assets.mszpro.com/2025/02/View-recent-photos-5.jpeg 1320w" sizes="auto, (max-width: 778px) 100vw, 778px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<p>You can see the Disney Resort Line train from on the deck of the boat</p>
</div>
</div>



<ul class="wp-block-list">
<li>Mickey Mouse greetings</li>
</ul>



<p>Then, I went to greet Mickey Mouse who was being a &#8220;tomb raider&#8221; in the<strong> Lost River Delta</strong> region.</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-3 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img decoding="async" data-id="643" src="https://static-assets.mszpro.com/2025/02/IMG_4756.heic" alt="" class="wp-image-643"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="644" src="https://static-assets.mszpro.com/2025/02/IMG_4757.heic" alt="" class="wp-image-644"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="645" src="https://static-assets.mszpro.com/2025/02/IMG_4758.heic" alt="" class="wp-image-645"/></figure>
</figure>



<p>You can give your phone to a crew and they will help you take a picture with Mickey Mouse! Don&#8217;t forget to high-five and smile!</p>



<ul class="wp-block-list">
<li>The new area! Fantasy Springs!</li>
</ul>



<p>Fantasy Springs is a new area opened! There is a beautiful lantern ride (but I did not get the chance to try it 😭 I will try it next time)</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】ファンタジースプリングス“ラプンツェルの森” | 東京ディズニ－シー/Tokyo DisneySea" width="500" height="281" src="https://www.youtube.com/embed/oV7ubVi05Fs?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<ul class="wp-block-list">
<li>Peter Pan&#8217;s Never Land Adventure</li>
</ul>



<p>I did use the DPA to access the Peter Pan&#8217;s Never Land Adventure! It is amazing! The animation and the effects are beyond my imagination. You feel like you are flying with the characters in the infinite sky!</p>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】ピーターパンのネバーランドアドベンチャー / Peter Pan&#039;s Never Land Adventure | 東京ディズニーシー/Tokyo DisneySea" width="500" height="281" src="https://www.youtube.com/embed/W_Sh7-_zD38?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>Day one was very very exciting! DisneySea is far beyond my imagination! DisneySea feels like a very new park (and indeed it is, with the new Fantasy Springs section)! It has a great layout, and for me, I went from the entrance on the south and played by going all the way North. Until I reach Fantasy Springs (which is on the north side, near the Bayside Disney Resort line station).</p>



<p>The cast members are very very friendly! They are so nice that I remember some of their names and submitted a positive feedback to the DisneySea.</p>



<h2 class="wp-block-heading">Day Two. Disneyland (8 attractions)</h2>



<ul class="wp-block-list">
<li>Big Thunder Mountain</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️🌟 Try it out! The wait is not too long! And it is very exciting, with a photo taken when you slide down to the water.</p>



<p>This is a very exciting small roller coaster, which starts with a trip similar to the This is a Small world, but in the close of the ride, you will go down a large slide into the water! It also takes a picture for you!</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-4 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="575" data-id="650" src="https://static-assets.mszpro.com/2025/02/IMG_13C9667CD0AD-1-1024x575.jpeg" alt="" class="wp-image-650" srcset="https://static-assets.mszpro.com/2025/02/IMG_13C9667CD0AD-1-300x168.jpeg 300w, https://static-assets.mszpro.com/2025/02/IMG_13C9667CD0AD-1-1024x575.jpeg 1024w, https://static-assets.mszpro.com/2025/02/IMG_13C9667CD0AD-1-768x431.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_13C9667CD0AD-1.jpeg 1320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="648" src="https://static-assets.mszpro.com/2025/02/IMG_4860.heic" alt="" class="wp-image-648"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="651" src="https://static-assets.mszpro.com/2025/02/IMG_4864.heic" alt="" class="wp-image-651"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="652" src="https://static-assets.mszpro.com/2025/02/IMG_4870.heic" alt="" class="wp-image-652"/></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="562" data-id="649" src="https://static-assets.mszpro.com/2025/02/IMG_B6646AD9FD04-1-1024x562.jpeg" alt="" class="wp-image-649" srcset="https://static-assets.mszpro.com/2025/02/IMG_B6646AD9FD04-1-300x165.jpeg 300w, https://static-assets.mszpro.com/2025/02/IMG_B6646AD9FD04-1-1024x562.jpeg 1024w, https://static-assets.mszpro.com/2025/02/IMG_B6646AD9FD04-1-768x421.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_B6646AD9FD04-1.jpeg 1320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="578" height="1024" data-id="653" src="https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-578x1024.jpeg" alt="" class="wp-image-653" srcset="https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-169x300.jpeg 169w, https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-578x1024.jpeg 578w, https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-768x1360.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-868x1536.jpeg 868w, https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1-1157x2048.jpeg 1157w, https://static-assets.mszpro.com/2025/02/IMG_CE0E53721C7F-1.jpeg 1320w" sizes="auto, (max-width: 578px) 100vw, 578px" /></figure>
</figure>



<p>Did you see the boat on top of the slide in the last picture! Yes! That is the most thrilling &amp; exciting part of this ride!</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】ビッグサンダー・マウンテン / Big Thunder Mountain | 東京ディズニーランド/Tokyo Disneyland" width="500" height="281" src="https://www.youtube.com/embed/FDcY5m7Pc4k?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p>Also, I only had to wait for about 10 minutes, and did not use a DPA here.</p>



<ul class="wp-block-list">
<li>Enchanted Tale of Beauty and the Beast</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<p>This ride has a super long line of waiting at standby, about 2 hours wait. So I purchased the DPA and got in within 10 minutes. </p>



<p>Before you take on the ride, you will walk into the mansion of the beast and it looks just like in the movie!</p>



<p>This attraction is so well designed, your seat will take you within the rooms of the Beauty and the Beast mansion, take you through the story, and you will join the dance party with the beauty and the beast! It is very high tech and creative!</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-5 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img decoding="async" data-id="654" src="https://static-assets.mszpro.com/2025/02/IMG_4885.heic" alt="" class="wp-image-654"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="657" src="https://static-assets.mszpro.com/2025/02/IMG_4890.heic" alt="" class="wp-image-657"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="656" src="https://static-assets.mszpro.com/2025/02/IMG_4893.heic" alt="" class="wp-image-656"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="655" src="https://static-assets.mszpro.com/2025/02/IMG_4894.heic" alt="" class="wp-image-655"/></figure>



<figure class="wp-block-image size-large"><img decoding="async" data-id="658" src="https://static-assets.mszpro.com/2025/02/IMG_4899.heic" alt="" class="wp-image-658"/></figure>
</figure>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="【公式】美女と野獣“魔法のものがたり” | 東京ディズニーランド/Tokyo Disneyland" width="500" height="281" src="https://www.youtube.com/embed/plcgsCkU0xw?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<ul class="wp-block-list">
<li>The Happy Ride with Baymax</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<p>This ride is so delight! The music is a hit! And it looks relaxing, but it gets exciting sometimes, with the car moving around quickly!</p>



<p>The wait is super long, around an hour, but a DPA gets me in within 5 minutes.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="561" src="https://static-assets.mszpro.com/2025/02/IMG_76C90B0CB32C-1-1024x561.jpeg" alt="" class="wp-image-659" srcset="https://static-assets.mszpro.com/2025/02/IMG_76C90B0CB32C-1-300x164.jpeg 300w, https://static-assets.mszpro.com/2025/02/IMG_76C90B0CB32C-1-1024x561.jpeg 1024w, https://static-assets.mszpro.com/2025/02/IMG_76C90B0CB32C-1-768x421.jpeg 768w, https://static-assets.mszpro.com/2025/02/IMG_76C90B0CB32C-1.jpeg 1320w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Club Mouse Beat</li>
</ul>



<p>For this one, you can try the &#8220;Standby Pass&#8221; option, since everyone has an assigned seat and there is no wait as like other attractions. However, Standby pass is like a lottery. I did not get the Standby pass, so I used DPA. But you should always try Standby pass first if it is available.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img decoding="async" src="https://static-assets.mszpro.com/2025/02/IMG_4927.heic" alt="" class="wp-image-660"/></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4938.mov" class="mcloud-attachment-661"></video></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://static-assets.mszpro.com/2025/02/IMG_4932-1024x576.jpg" alt="" class="wp-image-664" srcset="https://static-assets.mszpro.com/2025/02/IMG_4932-300x169.jpg 300w, https://static-assets.mszpro.com/2025/02/IMG_4932-1024x576.jpg 1024w, https://static-assets.mszpro.com/2025/02/IMG_4932-768x432.jpg 768w, https://static-assets.mszpro.com/2025/02/IMG_4932-1536x864.jpg 1536w, https://static-assets.mszpro.com/2025/02/IMG_4932-2048x1152.jpg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<ul class="wp-block-list">
<li>Parades</li>
</ul>



<p>There are many parades within the park. You can check it within the Disney Resort app by tapping on the list icon at the bottom right corner (besides the filter funnel) and see a list of shows.</p>



<figure class="wp-block-image size-full"><img decoding="async" src="https://static-assets.mszpro.com/2025/02/IMG_4956.heic" alt="" class="wp-image-663"/></figure>



<p>(I love the purple cat!)</p>



<ul class="wp-block-list">
<li>Mickey&#8217;s Magical Music World</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<p>This is very very touching! You see many of your favorite Disney characters show up, including Judy and Nick from Zootopia, of course Mickey Donald, but also Frozen, the Caribbean&#8217;s, and many many more! It takes your memory back in time. And the music and the show are very beautiful.</p>



<figure class="wp-block-image size-full"><img decoding="async" src="https://static-assets.mszpro.com/2025/02/IMG_4958.heic" alt="" class="wp-image-662"/></figure>



<p>For this show, you need to reserve a seat via Standby pass or purchase a DPA, similar to other shows.</p>



<ul class="wp-block-list">
<li>Little World</li>
</ul>



<p>This is a very classic attraction! But there are now Marvel elements like Groot!</p>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4945.mov" class="mcloud-attachment-666"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4950.mov" class="mcloud-attachment-665"></video></figure>



<ul class="wp-block-list">
<li>Reach for the Stars fireworks show</li>
</ul>



<p>Recommendation level: ⭐️⭐️⭐️⭐️🌟 MUST</p>



<p>If you need a mental recharge, this is it. I basically cried, looking at all the childhood Disney characters, and listening to the Reach for the Stars (you&#8217;ll find further than you know, if you can dream it, you can do it). It is so inspiring!</p>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4965.mov" class="mcloud-attachment-668"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4968.mov" class="mcloud-attachment-669"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4970.mov" class="mcloud-attachment-670"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4971.mov" class="mcloud-attachment-671"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4972.mov" class="mcloud-attachment-672"></video></figure>



<figure class="wp-block-video"><video controls src="https://static-assets.mszpro.com/2025/02/IMG_4973.mov" class="mcloud-attachment-673"></video></figure><p>The post <a href="https://mszpro.com/tokyo-disney-2025">Tokyo Disney 2-day travel guide – DisneySea & Land 2025 February – 15 Attractions</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4938.mov" length="7071924" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4945.mov" length="16475635" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4950.mov" length="8552962" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4965.mov" length="35487962" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4968.mov" length="24562991" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4970.mov" length="94695222" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4971.mov" length="25295193" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4972.mov" length="18794511" type="video/quicktime" />
<enclosure url="https://static-assets.mszpro.com/2025/02/IMG_4973.mov" length="43603661" type="video/quicktime" />

			</item>
		<item>
		<title>88&#215;31 logos</title>
		<link>https://mszpro.com/88x31-logos</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Wed, 25 Dec 2024 13:50:26 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=573</guid>

					<description><![CDATA[<p>Large (6.6MB) Medium (1.8 MB) Small (772 kb)</p>
<p>The post <a href="https://mszpro.com/88x31-logos">88×31 logos</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Large (6.6MB)</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="359" src="https://static-assets.mszpro.com/2024/12/mszpro-88x31-high-quality-1024x359.gif" alt="" class="wp-image-574" srcset="https://static-assets.mszpro.com/2024/12/mszpro-88x31-high-quality-300x105.gif 300w, https://static-assets.mszpro.com/2024/12/mszpro-88x31-high-quality-1024x359.gif 1024w, https://static-assets.mszpro.com/2024/12/mszpro-88x31-high-quality-768x269.gif 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Medium (1.8 MB)</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="622" height="218" src="https://static-assets.mszpro.com/2024/12/mszpro-88x31-medium-quality.gif" alt="" class="wp-image-575"/></figure>



<p>Small (772 kb)</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="416" height="146" src="https://static-assets.mszpro.com/2024/12/mszpro-88x31-low-quality.gif" alt="" class="wp-image-576"/></figure><p>The post <a href="https://mszpro.com/88x31-logos">88×31 logos</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Extract picked Memoji and stickers from UITextView (+SwiftUI compatible view)</title>
		<link>https://mszpro.com/memoji-textfield-picking</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Sat, 21 Dec 2024 13:08:06 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=569</guid>

					<description><![CDATA[<p>Sometimes, you might want to allow the user to pick their Memoji and stickers and upload them within your own app. To do that, you can present a keyboard and show Memojis and stickers. They will show on the keyboard if you have set the following properties for your UITextView: Then, you will set a [&#8230;]</p>
<p>The post <a href="https://mszpro.com/memoji-textfield-picking">Extract picked Memoji and stickers from UITextView (+SwiftUI compatible view)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Sometimes, you might want to allow the user to pick their Memoji and stickers and upload them within your own app.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="936" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_1C4A90F53238-1-936x1024.jpeg" alt="" class="wp-image-570" srcset="https://static-assets.mszpro.com/2024/12/IMG_1C4A90F53238-1-274x300.jpeg 274w, https://static-assets.mszpro.com/2024/12/IMG_1C4A90F53238-1-936x1024.jpeg 936w, https://static-assets.mszpro.com/2024/12/IMG_1C4A90F53238-1-768x840.jpeg 768w, https://static-assets.mszpro.com/2024/12/IMG_1C4A90F53238-1.jpeg 1320w" sizes="auto, (max-width: 936px) 100vw, 936px" /></figure>



<p>To do that, you can present a keyboard and show Memojis and stickers. They will show on the keyboard if you have set the following properties for your UITextView:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="textView.supportsAdaptiveImageGlyph = true
textView.allowsEditingTextAttributes = true" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">supportsAdaptiveImageGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">allowsEditingTextAttributes</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span></code></pre></div>



<p>Then, you will set a UITextView delegate <em>UITextViewDelegate</em> to receive a call whenever the content changed.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="textView.delegate = self" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">delegate</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">self</span></span></code></pre></div>



<p>Conform the view controller to <em>UITextViewDelegate</em>. Then implement the textViewDidChange function</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="func textViewDidChange(_ textView: UITextView) {
    if let attachment = findFirstAttachment(in: textView.attributedText) {
        handleMemoji(attachment: attachment)
        textView.text = &quot;&quot;
        return
    }
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">textViewDidChange</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">UITextView</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">findFirstAttachment</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">attributedText</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">handleMemoji</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">attachment</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> attachment</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        textView.text = &quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">        return</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>First, we try to find the attachment object that has Memoji within. We first check by type <em>adaptiveImageGlyph</em> (only available for iOS 18 and up), which is usually the case when you pick a sticker within iOS 18 system. And we check for <em>attachment</em> too.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="private func findFirstAttachment(in attributedText: NSAttributedString?) -&gt; NSTextAttachment? {
    guard let attributedText else { return nil }
    
    // First try to find NSAdaptiveImageGlyph
    var foundGlyph: NSTextAttachment?
    attributedText.enumerateAttribute(.adaptiveImageGlyph,
                                      in: NSRange(location: 0, length: attributedText.length),
                                      options: []) { value, range, stop in
        if let glyph = value as? NSAdaptiveImageGlyph {
            let attachment = NSTextAttachment()
            attachment.image = UIImage(data: glyph.imageContent)
            foundGlyph = attachment
            stop.pointee = true
        }
    }
    
    if let foundGlyph { return foundGlyph }
    
    // Fallback to regular attachment
    var foundAttachment: NSTextAttachment?
    attributedText.enumerateAttribute(.attachment,
                                      in: NSRange(location: 0, length: attributedText.length),
                                      options: []) { value, range, stop in
        if let attachment = value as? NSTextAttachment {
            foundAttachment = attachment
            stop.pointee = true
        }
    }
    return foundAttachment
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">findFirstAttachment</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attributedText</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">NSAttributedString</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    guard let attributedText else </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// First try to find NSAdaptiveImageGlyph</span></span>
<span class="line"><span style="color: #D8DEE9FF">    var </span><span style="color: #88C0D0">foundGlyph</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">enumerateAttribute</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">adaptiveImageGlyph</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                      </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #88C0D0">NSRange</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">location</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">length</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">length)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                      </span><span style="color: #D8DEE9">options</span><span style="color: #D8DEE9FF">: []) </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> stop </span><span style="color: #D8DEE9">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if let </span><span style="color: #D8DEE9">glyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">as</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSAdaptiveImageGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            let </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NSTextAttachment</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">            attachment.</span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">glyph</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">imageContent</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">            stop.</span><span style="color: #D8DEE9">pointee</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> return </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #ECEFF4">    </span><span style="color: #616E88">// Fallback to regular attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundAttachment</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">enumerateAttribute</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                      </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #88C0D0">NSRange</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">location</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">length</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">length)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                      </span><span style="color: #D8DEE9">options</span><span style="color: #D8DEE9FF">: []) </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> stop </span><span style="color: #D8DEE9">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">        if let </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">as</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">foundAttachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">            stop.</span><span style="color: #D8DEE9">pointee</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundAttachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span></code></pre></div>



<p>Then, if we have found such an text attachment, we extract the image:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="private func handleMemoji(attachment: NSTextAttachment) {
    if let image = attachment.image {
        self.pickedImage = image
    } else if let image = attachment.image(forBounds: attachment.bounds,
                                           textContainer: nil,
                                           characterIndex: 0) {
        self.pickedImage = image
    } else if let imageData = attachment.fileWrapper?.regularFileContents,
              let image = UIImage(data: imageData) {
        self.pickedImage = image
    }
    
#if DEBUG
    print(&quot;Memoji attachment handled: \(String(describing: self.pickedImage))&quot;)
#endif
}" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #D8DEE9">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleMemoji</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">image</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">forBounds</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">bounds</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                           </span><span style="color: #D8DEE9">textContainer</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">nil</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                           </span><span style="color: #D8DEE9">characterIndex</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">imageData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">fileWrapper</span><span style="color: #ECEFF4">?.</span><span style="color: #D8DEE9">regularFileContents</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">              </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> image </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">imageData</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">#</span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">DEBUG</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #88C0D0">print</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Memoji attachment handled: </span><span style="color: #EBCB8B">\(</span><span style="color: #A3BE8C">String(describing: self.pickedImage))</span><span style="color: #ECEFF4">&quot;</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">#</span><span style="color: #D8DEE9">endif</span></span>
<span class="line"><span style="color: #ECEFF4">}</span></span></code></pre></div>



<p>The below shows an example of a SwiftUI compatible view. If you are using UIKit, simple implement the delegate for your UITextView.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#2e3440ff"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// MARK: - StickerPickingTextView
@available(iOS 18.0, *)
struct StickerPickingTextView: UIViewRepresentable {
    @Binding var pickedImage: UIImage?
    @Binding var pickedEmoji: String
    
    func makeUIView(context: Context) -&gt; AdaptiveEmojiTextView {
        let textView = UITextView()
        textView.supportsAdaptiveImageGlyph = true
        textView.allowsEditingTextAttributes = true
        textView.delegate = context.coordinator
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) { return }
    
    func makeCoordinator() -&gt; Coordinator {
        Coordinator(pickedImage: $pickedImage, pickedEmoji: $pickedEmoji)
    }
    
    class Coordinator: NSObject, UITextViewDelegate {
        @Binding var pickedImage: UIImage?
        @Binding var pickedEmoji: String
        
        init(pickedImage: Binding&lt;UIImage?&gt;, pickedEmoji: Binding&lt;String&gt;) {
            self._pickedImage = pickedImage
            self._pickedEmoji = pickedEmoji
        }
        
        func textView(_ textView: UITextView,
                      shouldChangeTextIn range: NSRange,
                      replacementText text: String) -&gt; Bool {
            let newLength = (textView.text?.count ?? 0) + text.count - range.length
            return newLength &lt;= 1
        }
        
        func textViewDidChange(_ textView: UITextView) {
            // Handle Memoji and adaptive image glyphs
            if let attachment = findFirstAttachment(in: textView.attributedText) {
                handleMemoji(attachment: attachment)
                textView.text = &quot;&quot;
                return
            }
            
            // Handle regular emoji
            if let text = textView.text, !text.isEmpty {
                handleEmoji(text)
                textView.text = &quot;&quot;
            }
        }
        
        private func findFirstAttachment(in attributedText: NSAttributedString?) -&gt; NSTextAttachment? {
            guard let attributedText else { return nil }
            
            // First try to find NSAdaptiveImageGlyph
            var foundGlyph: NSTextAttachment?
            attributedText.enumerateAttribute(.adaptiveImageGlyph,
                                              in: NSRange(location: 0, length: attributedText.length),
                                              options: []) { value, range, stop in
                if let glyph = value as? NSAdaptiveImageGlyph {
                    let attachment = NSTextAttachment()
                    attachment.image = UIImage(data: glyph.imageContent)
                    foundGlyph = attachment
                    stop.pointee = true
                }
            }
            
            if let foundGlyph { return foundGlyph }
            
            // Fallback to regular attachment
            var foundAttachment: NSTextAttachment?
            attributedText.enumerateAttribute(.attachment,
                                              in: NSRange(location: 0, length: attributedText.length),
                                              options: []) { value, range, stop in
                if let attachment = value as? NSTextAttachment {
                    foundAttachment = attachment
                    stop.pointee = true
                }
            }
            return foundAttachment
        }
        
        private func handleMemoji(attachment: NSTextAttachment) {
            if let image = attachment.image {
                self.pickedImage = image
            } else if let image = attachment.image(forBounds: attachment.bounds,
                                                   textContainer: nil,
                                                   characterIndex: 0) {
                self.pickedImage = image
            } else if let imageData = attachment.fileWrapper?.regularFileContents,
                      let image = UIImage(data: imageData) {
                self.pickedImage = image
            }
            
#if DEBUG
            print(&quot;Memoji attachment handled: \(String(describing: self.pickedImage))&quot;)
#endif
        }
        
        private func handleEmoji(_ text: String) {
            self.pickedEmoji = text
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder),
                                            to: nil,
                                            from: nil,
                                            for: nil)
        }
    }
}
" style="color:#d8dee9ff;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki nord" style="background-color: #2e3440ff" tabindex="0"><code><span class="line"><span style="color: #616E88">// MARK: - StickerPickingTextView</span></span>
<span class="line"><span style="color: #D08770">@available</span><span style="color: #D8DEE9FF">(</span><span style="color: #D08770">iOS</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">18.0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">*</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9">struct</span><span style="color: #D8DEE9FF"> StickerPickingTextView</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">UIViewRepresentable</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D08770">@Binding</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedImage</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UIImage</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D08770">@Binding</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedEmoji</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> String</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">makeUIView</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">context</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">AdaptiveEmojiTextView</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UITextView</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">supportsAdaptiveImageGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">allowsEditingTextAttributes</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">delegate</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">coordinator</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">updateUIView</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">uiView</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">UITextView</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">context</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">Context</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">makeCoordinator</span><span style="color: #D8DEE9FF">() </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">Coordinator</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">Coordinator</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">$pickedImage</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedEmoji</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">$pickedEmoji</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span></span>
<span class="line"><span style="color: #D8DEE9FF">    </span><span style="color: #81A1C1">class</span><span style="color: #D8DEE9FF"> </span><span style="color: #8FBCBB">Coordinator</span><span style="color: #D8DEE9FF">: </span><span style="color: #8FBCBB">NSObject</span><span style="color: #D8DEE9FF">, </span><span style="color: #8FBCBB">UITextViewDelegate</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D08770">@Binding</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">var</span><span style="color: #D8DEE9FF"> pickedImage</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UIImage</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D08770">@Binding</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">var</span><span style="color: #D8DEE9FF"> pickedEmoji</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> String</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #88C0D0">init</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Binding</span><span style="color: #ECEFF4">&lt;</span><span style="color: #D8DEE9FF">UIImage</span><span style="color: #81A1C1">?</span><span style="color: #ECEFF4">&gt;,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedEmoji</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> Binding</span><span style="color: #ECEFF4">&lt;</span><span style="color: #D8DEE9FF">String</span><span style="color: #ECEFF4">&gt;)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">self</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">_pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedImage</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">self</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">_pickedEmoji</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">pickedEmoji</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">textView</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UITextView</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                      </span><span style="color: #D8DEE9">shouldChangeTextIn</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> NSRange</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                      </span><span style="color: #D8DEE9">replacementText</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">text</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> String</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> -&gt; Bool </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">newLength</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> (</span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">?.</span><span style="color: #D8DEE9">count</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">??</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">+</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">count</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">-</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">length</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">newLength</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">&lt;=</span><span style="color: #D8DEE9FF"> </span><span style="color: #B48EAD">1</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">textViewDidChange</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> UITextView</span><span style="color: #ECEFF4">)</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Handle Memoji and adaptive image glyphs</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">findFirstAttachment</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">attributedText</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">handleMemoji</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9">attachment</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> attachment</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                textView.text = &quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">                return</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Handle regular emoji</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #81A1C1">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">text</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">text</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> !</span><span style="color: #D8DEE9">text</span><span style="color: #D8DEE9FF">.isEmpty {</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #88C0D0">handleEmoji</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">text</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #D8DEE9">textView</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">text</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">&quot;&quot;</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">findFirstAttachment</span><span style="color: #D8DEE9FF">(</span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attributedText</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">NSAttributedString</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF">) </span><span style="color: #81A1C1">-&gt;</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            guard let attributedText else </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// First try to find NSAdaptiveImageGlyph</span></span>
<span class="line"><span style="color: #D8DEE9FF">            var </span><span style="color: #88C0D0">foundGlyph</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">enumerateAttribute</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">adaptiveImageGlyph</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                              </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #88C0D0">NSRange</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">location</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">length</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">length)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                              </span><span style="color: #D8DEE9">options</span><span style="color: #D8DEE9FF">: []) </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> stop </span><span style="color: #D8DEE9">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                if let </span><span style="color: #D8DEE9">glyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">as</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSAdaptiveImageGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    let </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">NSTextAttachment</span><span style="color: #D8DEE9FF">()</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    attachment.</span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">glyph</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">imageContent</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    stop.</span><span style="color: #D8DEE9">pointee</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> return </span><span style="color: #D8DEE9">foundGlyph</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #ECEFF4">            </span><span style="color: #616E88">// Fallback to regular attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">var</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundAttachment</span><span style="color: #81A1C1">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #81A1C1">?</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">enumerateAttribute</span><span style="color: #D8DEE9FF">(</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                              </span><span style="color: #81A1C1">in</span><span style="color: #D8DEE9FF">: </span><span style="color: #88C0D0">NSRange</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">location</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">length</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attributedText</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9FF">length)</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                              </span><span style="color: #D8DEE9">options</span><span style="color: #D8DEE9FF">: []) </span><span style="color: #ECEFF4">{</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">range</span><span style="color: #ECEFF4">,</span><span style="color: #D8DEE9FF"> stop </span><span style="color: #D8DEE9">in</span></span>
<span class="line"><span style="color: #D8DEE9FF">                if let </span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">value</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">as</span><span style="color: #81A1C1">?</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    </span><span style="color: #D8DEE9">foundAttachment</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">                    stop.</span><span style="color: #D8DEE9">pointee</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">true</span></span>
<span class="line"><span style="color: #D8DEE9FF">                </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            }</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #D8DEE9">return</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">foundAttachment</span></span>
<span class="line"><span style="color: #D8DEE9FF">        }</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleMemoji</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">attachment</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">NSTextAttachment</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            if let </span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #88C0D0">image</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">forBounds</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">bounds</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                   </span><span style="color: #D8DEE9">textContainer</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">nil</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                                   </span><span style="color: #D8DEE9">characterIndex</span><span style="color: #D8DEE9FF">: </span><span style="color: #B48EAD">0</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">else</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">if</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">let</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">imageData</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">attachment</span><span style="color: #ECEFF4">.</span><span style="color: #D8DEE9">fileWrapper</span><span style="color: #ECEFF4">?.</span><span style="color: #D8DEE9">regularFileContents</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                      let </span><span style="color: #D8DEE9">image</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">UIImage</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">data</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">imageData</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">                self.</span><span style="color: #D8DEE9">pickedImage</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">image</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span></span>
<span class="line"><span style="color: #D8DEE9FF">#if </span><span style="color: #D8DEE9">DEBUG</span></span>
<span class="line"><span style="color: #D8DEE9FF">            </span><span style="color: #88C0D0">print</span><span style="color: #ECEFF4">(</span><span style="color: #ECEFF4">&quot;</span><span style="color: #A3BE8C">Memoji attachment handled: </span><span style="color: #EBCB8B">\(</span><span style="color: #A3BE8C">String(describing: self.pickedImage))</span><span style="color: #ECEFF4">&quot;</span><span style="color: #ECEFF4">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">#endif</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #D8DEE9">private</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">func</span><span style="color: #D8DEE9FF"> </span><span style="color: #88C0D0">handleEmoji</span><span style="color: #D8DEE9FF">(</span><span style="color: #D8DEE9">_</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">text</span><span style="color: #D8DEE9FF">: </span><span style="color: #D8DEE9">String</span><span style="color: #D8DEE9FF">) </span><span style="color: #ECEFF4">{</span></span>
<span class="line"><span style="color: #D8DEE9FF">            self.</span><span style="color: #D8DEE9">pickedEmoji</span><span style="color: #D8DEE9FF"> </span><span style="color: #81A1C1">=</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">text</span></span>
<span class="line"><span style="color: #D8DEE9FF">            UIApplication.shared.sendAction(#</span><span style="color: #88C0D0">selector</span><span style="color: #ECEFF4">(</span><span style="color: #D8DEE9FF">UIResponder.resignFirstResponder</span><span style="color: #ECEFF4">),</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                            </span><span style="color: #88C0D0">to</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                            </span><span style="color: #88C0D0">from</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #ECEFF4">,</span></span>
<span class="line"><span style="color: #D8DEE9FF">                                            </span><span style="color: #88C0D0">for</span><span style="color: #ECEFF4">:</span><span style="color: #D8DEE9FF"> </span><span style="color: #D8DEE9">nil</span><span style="color: #D8DEE9FF">)</span></span>
<span class="line"><span style="color: #D8DEE9FF">        </span><span style="color: #ECEFF4">}</span></span>
<span class="line"><span style="color: #D8DEE9FF">    }</span></span>
<span class="line"><span style="color: #D8DEE9FF">}</span></span>
<span class="line"></span></code></pre></div><p>The post <a href="https://mszpro.com/memoji-textfield-picking">Extract picked Memoji and stickers from UITextView (+SwiftUI compatible view)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Mixi2 招待</title>
		<link>https://mszpro.com/mixi2-%e6%8b%9b%e5%be%85</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Tue, 17 Dec 2024 04:24:49 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=513</guid>

					<description><![CDATA[<p>&#8220;MIXIが提供する「繋がった人、繋がりたい人との関係性を深められる」ことを目指した新しいSNSです。&#8221; 招待コード：一緒にはじめよう！🚀https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w mszproからの #mixi2 招待🎟️一緒にはじめよう！🚀https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w</p>
<p>The post <a href="https://mszpro.com/mixi2-%e6%8b%9b%e5%be%85">Mixi2 招待</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>&#8220;MIXIが提供する「繋がった人、繋がりたい人との関係性を深められる」ことを目指した新しいSNSです。&#8221;</p>



<p>招待コード：<br>一緒にはじめよう！🚀<br><a href="https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w">https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w</a></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="504" height="1024" src="https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-504x1024.jpeg" alt="" class="wp-image-514" srcset="https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-148x300.jpeg 148w, https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-504x1024.jpeg 504w, https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-768x1560.jpeg 768w, https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-756x1536.jpeg 756w, https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-1008x2048.jpeg 1008w, https://static-assets.mszpro.com/2024/12/IMG_08D15755F423-1-scaled.jpeg 1260w" sizes="auto, (max-width: 504px) 100vw, 504px" /></figure>



<p>mszproからの #mixi2 招待🎟️<br>一緒にはじめよう！🚀<br><a href="https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w">https://mixi.social/invitations/@mszpro/YbHJh9E4wF8kKaY8hARm6w</a></p>



<p></p><p>The post <a href="https://mszpro.com/mixi2-%e6%8b%9b%e5%be%85">Mixi2 招待</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>SwiftUIアプリにLINEログインを追加、ユーザープロファイル情報の取得（.onOpenURLモディファイアを使用）</title>
		<link>https://mszpro.com/swiftui-line-login-ja</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 07:10:55 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=219</guid>

					<description><![CDATA[<p>この記事では、SwiftUIアプリでLineログインを統合し、ログイン成功時にユーザーのプロファイル情報を取得することについて説明します。 SwiftUIアプリとは SwiftUIアプリはApp構造体のみを持ち、AppDelegate&#160;ファイルや&#160;SceneDelegate&#160;ファイルは持っていません。 認証に成功した後、LineはアプリのカスタムURLスキーマを呼び出し、認証トークンの情報を提供します。 SwiftUIアプリでは、onOpenURLモディファイアがあり、ユーザーがLineで認証した後のログイン結果を処理するために使用することができます。 アプリの登録 まず、LINEのデベロッパーサイトにアクセスし、アプリケーションを登録します。 まず、プロバイダを作成します。これは会社名でも自分の名前でもかまいません。プロバイダーは複数のチャンネル（アプリケーション）を含むことができます。 次に、作成したプロバイダーをクリックし、アプリケーション（チャネル）を作成します。種類は、&#8221;LINEログイン&#8221;を選択します。 新しいチャンネルのフォームで、情報を入力します。&#8220;アプリの種類&#8221;で、&#8221;ネイティブアプリ &#8220;をトグルします。 リストに自分のチャンネルが表示されるので、クリックして開きます。 「LINEログイン設定」にて、iOSアプリケーションのバンドルIDを入力します。 ここで、「チャンネル基本設定」タブにある&#160;チャンネルID&#160;を覚えておきましょう また、チャンネルを公開とマークすることを確認してください。 SDKを統合 LineSDK&#160;はSwift Packageを使用しても配布されています。 プロジェクトに追加するには、Xcodeの「File」メニューをタップし、「Add Packages」をクリックしてください。以下のURLを入力します： そして、「LineSDK」にチェックを入れ、メインとなるiOSターゲットを選択し、フレームワークを追加します。 URLタイプの追加 ユーザーがLINEで認証されると、LINEはURLスキーマを使ってあなたのアプリを呼び出します。そのURLスキーマを定義する必要があります。プロジェクトファイルを開き、iOSアプリのターゲットを選択し、Info&#160;タブで、以下のURLスキーマを追加します。 line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER): SDKの初期化 SwiftUIアプリのファイル（@main`があるファイル）内で、SDKをインポートし、アプリ起動時にLine SDKを初期化するためのAppDelegateアダプタを追加します。 channelIDには、チャンネルページのチャンネルIDです。 認証結果の処理 SwiftUIアプリの場合、認証結果を処理するためにonOpenURLを使用する必要があります。SwiftUIアプリのファイルに、ログインを処理するコードを追加します： SwiftUI ビューにログインボタンを追加する ログインの状態を監視するために、observedオブジェクトを使用することにします。ここでの値は、ログインボタンを表示するビューに伝え返されます。また、より多くの情報を含むカスタム構造体を使用することもできます。 まず、SwiftUIでUIKit Lineログインボタンを表示するための互換ビューを作成する必要があります： ログインボタンを表示するビューにログインの状態を報告するために、LineLoginStatusを使用しています。その中で、@Published変数を使用し、これらの変数値の変更によってビューを更新します（そして.onChangeビューモディファイアを呼び出します）。 ##SwiftUIビューにLINEのログインボタンを追加する さて、SwiftUIのビューにボタンを追加することができます： onChange`ビューモディファイアを使って、ユーザーIDの変更（サインイン成功時）とエラーメッセージ（エラー時）を監視しています。 これで、このアプリを実行し、ユーザーがログインに成功したときにユーザーIDを確認することができます。 お読みいただきありがとうございました。 🐘 マストドン @me@mszpro.com ☺️ Twitter @MszPro ☺️&#160;サイト&#160;https://MszPro.com Written by MszPro~</p>
<p>The post <a href="https://mszpro.com/swiftui-line-login-ja">SwiftUIアプリにLINEログインを追加、ユーザープロファイル情報の取得（.onOpenURLモディファイアを使用）</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>この記事では、SwiftUIアプリでLineログインを統合し、ログイン成功時にユーザーのプロファイル情報を取得することについて説明します。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2Fe54b687e-6152-8361-6758-732859794bfb.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=362de5e3b396727b1bfdc47a6f7a1f76" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="1290" height="2796" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb.png" alt="" class="wp-image-233" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb-138x300.png 138w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb-472x1024.png 472w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb-768x1665.png 768w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb-709x1536.png 709w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb-945x2048.png 945w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe54b687e-6152-8361-6758-732859794bfb.png 1290w" sizes="auto, (max-width: 1290px) 100vw, 1290px" /></a></figure>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#swiftui%E3%82%A2%E3%83%97%E3%83%AA%E3%81%A8%E3%81%AF"></a>SwiftUIアプリとは</h2>



<p>SwiftUIアプリは<code>App</code>構造体のみを持ち、<code>AppDelegate</code>&nbsp;ファイルや&nbsp;<code>SceneDelegate</code>&nbsp;ファイルは持っていません。</p>



<pre class="wp-block-code"><code>import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
</code></pre>



<p>認証に成功した後、<br>LineはアプリのカスタムURLスキーマを呼び出し、<br>認証トークンの情報を提供します。</p>



<p>SwiftUIアプリでは、<br><code>onOpenURL</code>モディファイアがあり、<br>ユーザーがLineで認証した後のログイン結果を処理するために使用することができます。</p>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E7%99%BB%E9%8C%B2"></a>アプリの登録</h2>



<p>まず、LINEのデベロッパーサイトにアクセスし、アプリケーションを登録します。</p>



<figure class="wp-block-embed"><div class="wp-block-embed__wrapper">
https://qiita.com/embed-contents/link-card#qiita-embed-content__fcf0925a32bcc0ead812484bd66d3b26
</div></figure>



<p>まず、プロバイダを作成します。<br>これは会社名でも自分の名前でもかまいません。<br>プロバイダーは複数のチャンネル（アプリケーション）を含むことができます。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F975c1732-f6e2-3c43-56a0-9b154f216a5a.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=9d3e1920fee7e2da9c2195267877147a" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="578" height="301" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F975c1732-f6e2-3c43-56a0-9b154f216a5a.png" alt="Screenshot 2023-03-24 at 15.07.55.png" class="wp-image-221" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F975c1732-f6e2-3c43-56a0-9b154f216a5a-300x156.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F975c1732-f6e2-3c43-56a0-9b154f216a5a.png 578w" sizes="auto, (max-width: 578px) 100vw, 578px" /></a></figure>



<p>次に、作成したプロバイダーをクリックし、アプリケーション（チャネル）を作成します。<br>種類は、&#8221;LINEログイン&#8221;を選択します。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F0fd294d7-95ea-6dcd-739c-45d3f21a9da5.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=8b8b57ea7611ed11eba4c89e5e5d8723" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="860" height="444" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0fd294d7-95ea-6dcd-739c-45d3f21a9da5.png" alt="Screenshot 2023-03-24 at 15.11.06.png" class="wp-image-223" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0fd294d7-95ea-6dcd-739c-45d3f21a9da5-300x155.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0fd294d7-95ea-6dcd-739c-45d3f21a9da5-768x397.png 768w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0fd294d7-95ea-6dcd-739c-45d3f21a9da5.png 860w" sizes="auto, (max-width: 860px) 100vw, 860px" /></a></figure>



<p>新しいチャンネルのフォームで、<br>情報を入力します。<br>&#8220;アプリの種類&#8221;で、&#8221;ネイティブアプリ &#8220;をトグルします。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F6e562954-0cde-56b7-1bb3-875383618b88.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=c79a40180a63022a5cd13b16257fc3b8" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="693" height="161" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e562954-0cde-56b7-1bb3-875383618b88.png" alt="Screenshot 2023-03-24 at 15.11.32.png" class="wp-image-225" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e562954-0cde-56b7-1bb3-875383618b88-300x70.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e562954-0cde-56b7-1bb3-875383618b88.png 693w" sizes="auto, (max-width: 693px) 100vw, 693px" /></a></figure>



<p>リストに自分のチャンネルが表示されるので、クリックして開きます。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2Fef2a5dc7-46eb-630c-ce85-280993b1ce12.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=5a7b57bf328d5628aa1f317c9c915174" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="644" height="630" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fef2a5dc7-46eb-630c-ce85-280993b1ce12.png" alt="Screenshot 2023-03-24 at 15.10.54.png" class="wp-image-224" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fef2a5dc7-46eb-630c-ce85-280993b1ce12-300x293.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fef2a5dc7-46eb-630c-ce85-280993b1ce12.png 644w" sizes="auto, (max-width: 644px) 100vw, 644px" /></a></figure>



<p>「LINEログイン設定」にて、iOSアプリケーションのバンドルIDを入力します。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F6e38b3cf-fe79-3d94-3c86-de6cd8948baf.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=e6a9c2087326339b01473b8863bc6598" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="970" height="462" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e38b3cf-fe79-3d94-3c86-de6cd8948baf.png" alt="Screenshot 2023-03-24 at 16.07.27.png" class="wp-image-222" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e38b3cf-fe79-3d94-3c86-de6cd8948baf-300x143.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e38b3cf-fe79-3d94-3c86-de6cd8948baf-768x366.png 768w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F6e38b3cf-fe79-3d94-3c86-de6cd8948baf.png 970w" sizes="auto, (max-width: 970px) 100vw, 970px" /></a></figure>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F1e4f15aa-c15e-ad84-329e-d728abbe4587.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=f4672826dafdb39ff7acbba1236b9557" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="602" height="456" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F1e4f15aa-c15e-ad84-329e-d728abbe4587.png" alt="Screenshot 2023-03-24 at 15.13.13.png" class="wp-image-229" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F1e4f15aa-c15e-ad84-329e-d728abbe4587-300x227.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F1e4f15aa-c15e-ad84-329e-d728abbe4587.png 602w" sizes="auto, (max-width: 602px) 100vw, 602px" /></a></figure>



<p>ここで、「チャンネル基本設定」タブにある&nbsp;<code>チャンネルID</code>&nbsp;を覚えておきましょう</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2Fe7975084-4447-649f-b367-32bf4ceae3ba.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=6c20d607d2ffce311811434ad8b7ff5b" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="610" height="272" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe7975084-4447-649f-b367-32bf4ceae3ba.png" alt="Screenshot 2023-03-24 at 15.12.44.png" class="wp-image-228" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe7975084-4447-649f-b367-32bf4ceae3ba-300x134.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fe7975084-4447-649f-b367-32bf4ceae3ba.png 610w" sizes="auto, (max-width: 610px) 100vw, 610px" /></a></figure>



<p>また、チャンネルを公開とマークすることを確認してください。</p>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#sdk%E3%82%92%E7%B5%B1%E5%90%88"></a>SDKを統合</h2>



<p><code>LineSDK</code>&nbsp;はSwift Packageを使用しても配布されています。</p>



<p>プロジェクトに追加するには、Xcodeの「File」メニューをタップし、「Add Packages」をクリックしてください。以下のURLを入力します：</p>



<figure class="wp-block-embed"><div class="wp-block-embed__wrapper">
https://github.com/line/line-sdk-ios-swift.git
</div></figure>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F9a27c4c8-9548-60db-a511-1a34e071d56e.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=76c8e946c38c888bdf8216dd801a7803" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="1120" height="645" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9a27c4c8-9548-60db-a511-1a34e071d56e.png" alt="Screenshot 2023-03-24 at 15.20.11.png" class="wp-image-231" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9a27c4c8-9548-60db-a511-1a34e071d56e-300x173.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9a27c4c8-9548-60db-a511-1a34e071d56e-1024x590.png 1024w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9a27c4c8-9548-60db-a511-1a34e071d56e-768x442.png 768w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9a27c4c8-9548-60db-a511-1a34e071d56e.png 1120w" sizes="auto, (max-width: 1120px) 100vw, 1120px" /></a></figure>



<p>そして、「LineSDK」にチェックを入れ、メインとなるiOSターゲットを選択し、フレームワークを追加します。</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F0bab3195-1ba1-51a6-47a3-05d2646bb776.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=35f2daf7e447c1aff389acf782ec7ee8" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="695" height="334" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0bab3195-1ba1-51a6-47a3-05d2646bb776.png" alt="Screenshot 2023-03-24 at 15.20.16.png" class="wp-image-227" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0bab3195-1ba1-51a6-47a3-05d2646bb776-300x144.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F0bab3195-1ba1-51a6-47a3-05d2646bb776.png 695w" sizes="auto, (max-width: 695px) 100vw, 695px" /></a></figure>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#url%E3%82%BF%E3%82%A4%E3%83%97%E3%81%AE%E8%BF%BD%E5%8A%A0"></a>URLタイプの追加</h2>



<p>ユーザーがLINEで認証されると、LINEはURLスキーマを使ってあなたのアプリを呼び出します。そのURLスキーマを定義する必要があります。<br>プロジェクトファイルを開き、iOSアプリのターゲットを選択し、<code>Info</code>&nbsp;タブで、以下のURLスキーマを追加します。</p>



<p><code>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</code>:</p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=7760b552c5db48deeb2b976b6e8a19a1" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="1592" height="494" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e.png" alt="Screenshot 2023-03-24 at 15.23.31.png" class="wp-image-232" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e-300x93.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e-1024x318.png 1024w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e-768x238.png 768w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e-1536x477.png 1536w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302Fd1ad7b34-4596-6b41-1f90-d15a9d72b66e.png 1592w" sizes="auto, (max-width: 1592px) 100vw, 1592px" /></a></figure>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#sdk%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96"></a>SDKの初期化</h2>



<p>SwiftUIアプリのファイル（<a href="https://qiita.com/main">@main</a>`があるファイル）内で、<br>SDKをインポートし、<br>アプリ起動時にLine SDKを初期化するためのAppDelegateアダプタを追加します。</p>



<pre class="wp-block-code"><code>import SwiftUI
+import LineSDK

@main
struct LearnEnglishGPTApp: App {
    
+    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

+class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowSceneDelegate {
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: &#91;UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool {
+        LineSDK.LoginManager.shared.setup(channelID: "ooooooooo", universalLinkURL: nil)
+        return true
+    }    
+}

</code></pre>



<p><code>channelID</code>には、チャンネルページのチャンネルIDです。</p>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#%E8%AA%8D%E8%A8%BC%E7%B5%90%E6%9E%9C%E3%81%AE%E5%87%A6%E7%90%86"></a>認証結果の処理</h2>



<p>SwiftUIアプリの場合、<br>認証結果を処理するために<code>onOpenURL</code>を使用する必要があります。<br>SwiftUIアプリのファイルに、<br>ログインを処理するコードを追加します：</p>



<pre class="wp-block-code"><code>import SwiftUI
import LineSDK

@main
struct LearnEnglishGPTApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
+                .onOpenURL { openedURL in
+                    if openedURL.absoluteString.contains("line3rdp") {
+                        _ = LineSDK.LoginManager.shared.application(UIApplication.shared, open: openedURL)
+                    }
                }
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate, UIWindowSceneDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: &#91;UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool {
        LineSDK.LoginManager.shared.setup(channelID: "ooooooooo", universalLinkURL: nil)
        return true
    }
    
}
</code></pre>



<h2 class="wp-block-heading"><a href="https://qiita.com/mashunzhe/items/8fe33ccac6f31db3771e#swiftui-%E3%83%93%E3%83%A5%E3%83%BC%E3%81%AB%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%83%9C%E3%82%BF%E3%83%B3%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B"></a>SwiftUI ビューにログインボタンを追加する</h2>



<p>ログインの状態を監視するために、<code>observed</code>オブジェクトを使用することにします。<br>ここでの値は、ログインボタンを表示するビューに伝え返されます。<br>また、より多くの情報を含むカスタム構造体を使用することもできます。</p>



<pre class="wp-block-code"><code>class LineLoginStatus: NSObject, ObservableObject {
    @Published var isLoggingIn: Bool = false
    @Published var signInSuccessful_userID: String?
    @Published var errorMessage: String?
}
</code></pre>



<p>まず、SwiftUIでUIKit Lineログインボタンを表示するための互換ビューを作成する必要があります：</p>



<pre class="wp-block-code"><code>import SwiftUI
import LineSDK

struct LineLoginButtonCompatibleView: UIViewRepresentable {
    
    @ObservedObject var stateManager: LineLoginStatus
    
    func makeUIView(context: Context) -&gt; UIView {
        let containerView = UIView()
        let loginButton = LoginButton()
        loginButton.delegate = context.coordinator
        loginButton.permissions = &#91;.profile, .openID]
        containerView.addSubview(loginButton)
        // auto layout
        loginButton.translatesAutoresizingMaskIntoConstraints = false
        let centerX = loginButton.centerXAnchor.constraint(equalTo: containerView.centerXAnchor)
        let centerY = loginButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor)
        NSLayoutConstraint.activate(&#91;centerX, centerY])
        return containerView
    }
    
    func updateUIView(_ view: UIView, context: Context) { }
    
    func makeCoordinator() -&gt; Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, LoginButtonDelegate {
        
        var parent: LineLoginButtonCompatibleView
        
        init(_ parentView: LineLoginButtonCompatibleView) {
            self.parent = parentView
        }
        
        func loginButton(_ button: LoginButton, didSucceedLogin loginResult: LoginResult) {
            print("Line login success! \(loginResult.userProfile?.userID) \(loginResult.userProfile?.displayName) \(loginResult.accessToken.value)")
            parent.stateManager.signInSuccessful_userID = loginResult.userProfile?.userID
            parent.stateManager.isLoggingIn = false
        }
        
        func loginButton(_ button: LoginButton, didFailLogin error: LineSDKError) {
            print("Line Login Error \(error.localizedDescription)")
            parent.stateManager.isLoggingIn = false
            parent.stateManager.errorMessage = error.localizedDescription
        }
        
        func loginButtonDidStartLogin(_ button: LoginButton) {
            print("Line login started")
            parent.stateManager.isLoggingIn = true
        }
    }
    
}
</code></pre>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F9c1002c6-fd2c-c9b0-3942-e0b710baf53a.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=984a8d02eeebec1108112a6db72a046b" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="518" height="254" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9c1002c6-fd2c-c9b0-3942-e0b710baf53a.png" alt="Screenshot 2023-03-24 at 15.43.24.png" class="wp-image-226" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9c1002c6-fd2c-c9b0-3942-e0b710baf53a-300x147.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F9c1002c6-fd2c-c9b0-3942-e0b710baf53a.png 518w" sizes="auto, (max-width: 518px) 100vw, 518px" /></a></figure>



<p>ログインボタンを表示するビューにログインの状態を報告するために、<code>LineLoginStatus</code>を使用しています。<br>その中で、<br><code>@Published</code>変数を使用し、<br>これらの変数値の変更によってビューを更新します<br>（そして.onChangeビューモディファイアを呼び出します）。</p>



<p>##SwiftUIビューにLINEのログインボタンを追加する</p>



<p>さて、SwiftUIのビューにボタンを追加することができます：</p>



<pre class="wp-block-code"><code>struct SignInSheet: View {
    
    @ObservedObject var lineLoginStatus: LineLoginStatus = .init()
    
    @State private var loggedInUserID: String?
    @State private var errorMessage: String?
    @State private var isProcessingLogin: Bool = false
    
    var body: some View {
        
        Form {
            
            Section("Button") {
                LineLoginButton(stateManager: self.lineLoginStatus)
                    .onChange(of: self.lineLoginStatus.signInSuccessful_userID) { newValue in
                        if let newValue {
                            DispatchQueue.main.async {
                                self.loggedInUserID = newValue
                            }
                        }
                    }
                    .onChange(of: self.lineLoginStatus.errorMessage) { newValue in
                        if let newValue {
                            DispatchQueue.main.async {
                                self.errorMessage = newValue
                            }
                        }
                    }
                    .onChange(of: self.lineLoginStatus.isLoggingIn) { newValue in
                        DispatchQueue.main.async {
                            self.isProcessingLogin = newValue
                        }
                    }
                    .frame(height: 45)
                    .padding(.top, 5)
            }
            
            Section("Result") {
                if isProcessingLogin {
                    ProgressView()
                }
                if let loggedInUserID {
                    Label(loggedInUserID, systemImage: "checkmark.circle")
                }
                if let errorMessage {
                    Label(errorMessage, systemImage: "xmark")
                }
            }
            
        }
        
    }
}
</code></pre>



<p>onChange`ビューモディファイアを使って、ユーザーIDの変更（サインイン成功時）とエラーメッセージ（エラー時）を監視しています。</p>



<p>これで、このアプリを実行し、ユーザーがログインに成功したときにユーザーIDを確認することができます。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>お読みいただきありがとうございました。</p>



<p><a href="https://sns.mszpro.com/@me" rel="noreferrer noopener" target="_blank">🐘 マストドン @me@mszpro.com</a></p>



<p><a href="https://twitter.com/MszPro" rel="noreferrer noopener" target="_blank">☺️ Twitter @MszPro</a></p>



<p><strong>☺️</strong>&nbsp;サイト&nbsp;<a href="https://mszpro.com/" rel="noreferrer noopener" target="_blank">https://MszPro.com</a></p>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.mszmagic.com%2Fstatic-web-content%2FAppClipImage_small.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=a4fb7ab3d958113fcae3f48ae6b179cd" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="365" height="600" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fcdn.mszmagic.com2Fstatic-web-content2FAppClipImage_small.png" alt="" class="wp-image-220" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fcdn.mszmagic.com2Fstatic-web-content2FAppClipImage_small-183x300.png 183w, https://static-assets.mszpro.com/2024/12/https3A2F2Fcdn.mszmagic.com2Fstatic-web-content2FAppClipImage_small.png 365w" sizes="auto, (max-width: 365px) 100vw, 365px" /></a></figure>



<figure class="wp-block-image"><a href="https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F635330%2F640938e9-c121-8152-b8c4-8ab63124afcc.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=2d3be27b060c9216730d082739a21e0d" target="_blank" rel="noreferrer noopener"><img loading="lazy" decoding="async" width="400" height="300" src="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F640938e9-c121-8152-b8c4-8ab63124afcc.png" alt="writing-quickly_emoji_400.png" class="wp-image-230" srcset="https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F640938e9-c121-8152-b8c4-8ab63124afcc-300x225.png 300w, https://static-assets.mszpro.com/2024/12/https3A2F2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com2F02F6353302F640938e9-c121-8152-b8c4-8ab63124afcc.png 400w" sizes="auto, (max-width: 400px) 100vw, 400px" /></a></figure>



<p>Written by MszPro~</p><p>The post <a href="https://mszpro.com/swiftui-line-login-ja">SwiftUIアプリにLINEログインを追加、ユーザープロファイル情報の取得（.onOpenURLモディファイアを使用）</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>カメラからQRコードの検出、ハイライト表示、SwiftUI対応ビューの作成</title>
		<link>https://mszpro.com/swiftui-camera-qr-scan-ja</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 07:03:23 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=217</guid>

					<description><![CDATA[<p>カメラからのビデオストリームを表示し、QRコードなどを検出して強調表示するSwiftUI対応ビューの作成方法を解説します。AVCaptureMetadataOutput, SwiftUI, QRスキャン</p>
<p>The post <a href="https://mszpro.com/swiftui-camera-qr-scan-ja">カメラからQRコードの検出、ハイライト表示、SwiftUI対応ビューの作成</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p id="tw-target-text"></p>



<p>この記事では、カメラからのビデオストリームを表示し、<br>QRコード（または他のタイプのコード）を検出し、<br>その周りに矩形を表示することによってコードを強調するビューを作成することについて話します。<br>また、SwiftUI互換のビューを作成し、SwiftUIのビュー内で使用できるようにします。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="295" height="206" src="https://static-assets.mszpro.com/2024/12/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_635330_94abc373-5262-de1b-0d03-130b4166d1af.avif" alt="" class="wp-image-211"/></figure>



<h1 class="wp-block-heading"><strong>変数を用意する</strong></h1>



<p>まず、スキャンした結果とカメラプレビューレイヤーを保存するために、以下の変数を追加します。</p>



<script src="https://gist.github.com/mszpro/4368c42ff20fe900c4357944d3b7d816.js"></script>



<p><code>viewSize</code> はカメラプレビューレイヤーのサイズを表します。</p>



<h1 class="wp-block-heading"><strong>プレビューレイヤーと検出されたQRコードのオーバーレイビューを初期化</strong></h1>



<script src="https://gist.github.com/mszpro/edcda28e0b483ea645787084e06a57fa.js"></script>



<p>ビデオプレビューレイヤーを初期化し、サイズを設定します。</p>



<p><code>qrCodeFrameView</code> は、検出されたQRコードに重ねて表示される (オーバーレイ) ビューです。<br>現在はフレームを持ちません。<br>しかし、QRコードを検出したときに、そのビューの位置とサイズを設定することになります。</p>



<h1 class="wp-block-heading"><strong>ビデオ撮影用カメラデバイスを取得</strong></h1>



<p>ビデオキャプチャーのためのデフォルトのカメラデバイスを取得します。<br>そして、ビデオキャプチャセッションにカメラ入力を追加します。</p>



<script src="https://gist.github.com/mszpro/6f09a86b62f72305f53ffcc8f6127dec.js"></script>



<p>** <code>do</code> と <code>catch</code> ブロックの使い分けを忘れないように。</p>



<h1 class="wp-block-heading"><strong>メタデータ出力の追加（QRコード検出用）</strong></h1>



<p>バーコードや物体検出にもVisionフレームワークを利用することができます。</p>



<p><a href="https://qiita.com/MaShunzhe/items/d2f82bd920eeb2e82167">https://qiita.com/MaShunzhe/items/d2f82bd920eeb2e82167</a></p>



<p>しかし、この機能はすでにAVFoundationフレームワークの中で提供されているので、代わりに<code>AVCaptureMetadataOutput</code>を使用することにします。</p>



<p><code>AVCaptureMetadataOutput</code> を使用すると、検出されたメタデータオブジェクトは <code>setMetadataObjectsDelegate</code> 関数で定義されたデリゲートに報告されます。</p>



<script src="https://gist.github.com/mszpro/8b88e5fd9d6b08801ef5218957f565c3.js"></script>



<p><code>metadataObjectTypes</code> は、QRコードを探すためのコードを定義していますが、<br>バーコードや他の多くの種類を検出するように設定することも可能です。</p>



<h1 class="wp-block-heading"><strong>ビデオセッションを開始</strong></h1>



<p>ビデオのプレビューをレイヤーとしてビューに追加します（ユーザーは何がスキャンされているのか見ることができます）。</p>



<p>その後、ビデオセッションの実行を開始します。</p>



<p>また、<code>qrCodeFrameView</code> をビューに追加します。<br>また、他のビューの上にオーバーレイビューを表示 (<code>bringSubviewToFront</code>) しています<br>この時点では、サイズを持たないので、<code>qrCodeFrameView</code>が画面には表示されません。</p>



<script src="https://gist.github.com/mszpro/fdf97648de80222e739db6b74f16f858.js"></script>



<h1 class="wp-block-heading"><strong>メタデータ出力デリゲートを設定</strong></h1>



<script src="https://gist.github.com/mszpro/dbaf5711eb56be20f79c73fee51b5f9d.js"></script>



<p>QRコードのメタデータが検出されたら、<br><code>qrCodeFrameView</code> のフレームを検出されたバーコードオブジェクトのバウンドに設定し、<br>ビューの境界の色を緑に設定することができます。</p>



<p>コードが検出されない場合は、<br>フレームを0に設定してビューを非表示にすることができます。</p>



<p>** ここで、デバイスの向きの変更も検知し、それを利用してプレビューの向きを変更する必要の場合があります。</p>



<h1 class="wp-block-heading"><strong>SwiftUIでUIKitのビューを対応させる</strong></h1>



<p>SwiftUIでUIKitの <code>UIView</code> を互換性のあるものにするために、<br>UIViewRepresentableを使用することにします。</p>



<p>初期化するために <code>func makeUIView(context: Context) -&gt; UIView</code> 関数が必要です。</p>



<p>デリゲート関数は <code>Coordinator</code> クラス内に配置されることになる。 <code>class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate</code></p>



<p>そして、<code>func makeCoordinator() -&gt; QRCodeScanner.Coordinator</code> 関数内で <code>Coordinator</code>を初期化します。</p>



<p>また、変数の更新に基xづいてUIKitビューを更新する関数 <code>func updateUIView(_ view: UIView, context: Context)</code> を用意する必要があります。<br>ここでは、スキャンしたQRコードの値を更新しているので、その関数内では何もする必要がありません。</p>



<p>完成したSwiftUI互換のビューはこちらです。</p>



<script src="https://gist.github.com/mszpro/43ecc094bb56b8aba79f4fc742711440.js"></script>



<p>このコードをSwiftUIで使用するために</p>



<script src="https://gist.github.com/mszpro/c64ad20a86f66ff0f97eecf5da6451e2.js"></script>



<p>また、Info.plistファイル内にカメラの使用の説明テキストを追加することを忘れないでください。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>お読みいただきありがとうございました。</p>



<p><a href="https://sns.mszpro.com/@me">🐘 マストドン @me@mszpro.com</a></p>



<p><a href="https://twitter.com/MszPro">☺️ Twitter @MszPro</a></p>



<p><strong>☺️</strong> サイト <a href="https://mszpro.com/">https://MszPro.com</a></p><p>The post <a href="https://mszpro.com/swiftui-camera-qr-scan-ja">カメラからQRコードの検出、ハイライト表示、SwiftUI対応ビューの作成</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Use iCloud CloudKit &#038; Sign in with Apple to keep user’s information (UIKit)</title>
		<link>https://mszpro.com/cloudkit-user-info-save-uikit</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 06:07:44 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=208</guid>

					<description><![CDATA[<p>Are you using a third party database to keep user’s information? If you are only developing for the iOS platform, you can consider to use iCloud CloudKit with Sign in with Apple. What will we accomplish? Let’s get&#160;started Step 1. Turn on the required capabilities in Xcode project settings. Turn on “CloudKit” in “iCloud” Now [&#8230;]</p>
<p>The post <a href="https://mszpro.com/cloudkit-user-info-save-uikit">Use iCloud CloudKit & Sign in with Apple to keep user’s information (UIKit)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Are you using a third party database to keep user’s information? If you are only developing for the iOS platform, you can consider to use iCloud CloudKit with Sign in with Apple.</p>



<h1 class="wp-block-heading">What will we accomplish?</h1>



<ol class="wp-block-list">
<li>Users can sign up your app with “Sign in with Apple”</li>



<li>Users can sign in again on any of their devices (signed in with the same Apple ID) and you can retrieve the name, email, and other user information you saved.</li>
</ol>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*f-fJwXoA2Abpw6oJ2wvVag.png" alt=""/></figure>



<h3 class="wp-block-heading">Let’s get&nbsp;started</h3>



<p><strong>Step 1. Turn on the required capabilities in Xcode project settings. Turn on “CloudKit” in “iCloud”</strong></p>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*i2ZMbLm8R10phJEyo4OGDg.png" alt=""/></figure>



<p>Now turn on “CloudKit”</p>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*WfGRKVRAnvhmFElpI7y6yQ.png" alt=""/></figure>



<p>Click to toggle one of the existing “Containers” or click the plus icon to add a new one.</p>



<p><strong>Step 2. Set up the CloudKit data structure for storing the user information</strong></p>



<ul class="wp-block-list">
<li>Now click on “CloudKit Dashboard” button or go to <a href="http://icloud.developer.apple.com/" target="_blank" rel="noreferrer noopener">http://icloud.developer.apple.com</a></li>



<li>Click the name of your application on the left side of the panel.</li>



<li>Click on Schema</li>
</ul>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*03mnWGElQAhabu40qdKixg.png" alt=""/></figure>



<ul class="wp-block-list">
<li>Click on New Type</li>
</ul>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*slp5opolLcxmNUztCiZdow.png" alt=""/></figure>



<p>Name the type “UserInfo” (same as the type you write in your code), select the type you just created.</p>



<p>Click on “Add field button” on the right and add the following new fields:</p>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*fIdIuX26LEHH7ZkaPRiu4A.png" alt=""/></figure>



<p>“emailAddress” and “name” are both “String”</p>



<p><strong>Step 3. Add the capability “Sign in with Apple”</strong></p>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*yMZDtZbfiHj8bXaBYYOyAw.png" alt=""/></figure>



<p>Now your “Capabilities” tab should contain at least these two:</p>



<figure class="wp-block-image"><img decoding="async" src="https://cdn-images-1.medium.com/max/1600/1*arDUxSQJwB453PIJ9zFTMw.png" alt=""/></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you are lazy, the below codes can also be obtained here:&nbsp;<a href="https://github.com/mszopensource/CloudKitAppleSignInExample/blob/master/loginViewController.swift" rel="noreferrer noopener" target="_blank">https://github.com/mszopensource/CloudKitAppleSignInExample/blob/master/loginViewController.swift</a></p>
</blockquote>



<p><strong>Step 4. Create a new login view controller (skip if you already did that)</strong></p>



<pre class="wp-block-code"><code>import Foundation

import UIKit

class loginViewController: UIViewController {

    override func viewDidLoad() {

        super.viewDidLoad()

    }

}</code></pre>



<p><strong>Import the necessary frameworks</strong></p>



<pre class="wp-block-code"><code>import AuthenticationServices

import CloudKit</code></pre>



<p><strong>Add a reference to the “Sign in with Apple button”</strong></p>



<pre class="wp-block-code"><code>@IBOutlet weak var signInBtnView: UIView!
var authorizationButton: ASAuthorizationAppleIDButton!</code></pre>



<p>At here, I am adding thhe sign in button to an existing view called `signInBtnView`. `signInBtnView` has been added using UIStoryBoard here.</p>



<p><strong>Add that button to your view</strong></p>



<pre class="wp-block-code"><code>override func viewDidLoad() {

    super.viewDidLoad()

     authorizationButton = ASAuthorizationAppleIDButton(type: .default, style: .whiteOutline)

    authorizationButton.frame = CGRect(origin: .zero, size: signInBtnView.frame.size)

    authorizationButton.addTarget(self, action: #selector(handleAppleIdRequest), for: .touchUpInside)

    signInBtnView.addSubview(authorizationButton)

}</code></pre>



<p><strong>Handle button click action</strong></p>



<pre class="wp-block-code"><code>@objc func handleAppleIdRequest(){

    let appleIDProvider = ASAuthorizationAppleIDProvider()

    let request = appleIDProvider.createRequest()

    request.requestedScopes = &#91;.fullName, .email]

    let authorizationController =     ASAuthorizationController(authorizationRequests: &#91;request])

    authorizationController.delegate = self

    authorizationController.performRequests()

}</code></pre>



<p><strong>Set up the delegate to receive the result / error</strong></p>



<pre class="wp-block-code"><code>extension loginViewController: ASAuthorizationControllerDelegate {

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

    //Success

}

func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {

    //Failed

    print(error.localizedDescription)

}</code></pre>



<p><strong>Now you should have Sign in with Apple up and running. Test it now in your simulator!</strong></p>



<p>However, we still need to: 1. Obtain user information from the sign-in; 2. Keep or fetch from iCloud</p>



<p><strong>Fetch information from the sign-in</strong></p>



<pre class="wp-block-code"><code>func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

    if let appleIDCredential = authorization.credential as?    ASAuthorizationAppleIDCredential {
        let userID = appleIDCredential.user
        let name = appleIDCredential.fullName?.givenName
        let emailAddr = appleIDCredential.email
    }
}</code></pre>



<p><strong>Now here’s an important information:</strong></p>



<ol class="wp-block-list">
<li>If it’s the first time a user uses “Sign in with Apple” with your app, you will receive the userID, name, and email address. We will work with CloudKit in later part of this article to save that information.</li>



<li>If user is returning (signing in instead of signing up), then only the userID will be provided. We will work with CloudKit in later part of this article to learn how to fetch the name and email with the userID.</li>



<li>Note that userID is always the same for one user</li>
</ol>



<p><strong>Check if user is signing up (new user) or signing in</strong></p>



<pre class="wp-block-code"><code>func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

    if let appleIDCredential = authorization.credential as?  ASAuthorizationAppleIDCredential {

        let userID = appleIDCredential.user

        if let name = appleIDCredential.fullName?.givenName,

        let emailAddr = appleIDCredential.email {

            //New user (Signing up).

            //Save this information to CloudKit

        } else {

            //Returning user (signing in)

            //Fetch the user name/ email address

            //from private CloudKit

        }

    }

}</code></pre>



<p>If there is a name and email provided (in other words, user is signing up), we can create a new CloudKit record:</p>



<p>Note that we assign the ‘recordID’ to be the same as ‘userID’ we obtained from “Sign in with Apple” so that we can fetch user’s CloudKit record later.</p>



<pre class="wp-block-code"><code>//New user (Signing up).

//Save this information to CloudKit

let record = CKRecord(recordType: "UserInfo", recordID: CKRecord.ID(recordName: userID))

record&#91;"name"] = name

record&#91;"emailAddress"] = emailAddr

privateDatabase.save(record) { (_, _) in

UserDefaults.standard.set(record.recordID.recordName, forKey: "userProfileID")</code></pre>



<p>If returning user, we will fetch the name and email:</p>



<pre class="wp-block-code"><code>//Returning user (signing in)

//Fetch the user name/ email address

//from private CloudKit

privateDatabase.fetch(withRecordID: CKRecord.ID(recordName: userID)) { (record, error) in

    if let fetchedInfo = record {

       let name = fetchedInfo&#91;"name"] as? String

        let userEmailAddr = fetchedInfo&#91;"emailAddress"] as? String

        //You can now use the user name and email address (like save it to local)
        UserDefaults.standard.set(name, forKey: "userName")

        UserDefaults.standard.set(userID, forKey: "userProfileID")

}</code></pre>



<p>Completed code:</p>



<p><a href="https://github.com/mszopensource/CloudKitAppleSignInExample/blob/master/loginViewController.swift"><strong>mszopensource/CloudKitAppleSignInExample</strong><br></a><a href="https://github.com/mszopensource/CloudKitAppleSignInExample/blob/master/loginViewController.swift"></a></p>



<p><strong>Also note that you’ll need to deploy the iCloud CloudKit database to production be you publish your app.</strong></p><p>The post <a href="https://mszpro.com/cloudkit-user-info-save-uikit">Use iCloud CloudKit & Sign in with Apple to keep user’s information (UIKit)</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>iOSアプリで画面上のQRコード部分のみ輝度を上げ（Metal, EDRレンダリング）</title>
		<link>https://mszpro.com/ios-metal-edr</link>
		
		<dc:creator><![CDATA[msz]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 05:58:54 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://mszpro.com/?p=205</guid>

					<description><![CDATA[<p>WWDC 2022で発表されたMetalの新機能では、EDRを使って画面画像の一部分だけを明るく（現在設定されている画面の明るさ以上）することができます。 部屋の明かりを消して最小限の明るさの画面 背景 QRコード決済画面を表示する場合、通常、画面全体の輝度が高くなり（夜間に開くと気分が悪くなることがある。。。） また、アプリ側で輝度の設定と復元を手動で行う必要があります。 WWDC 2022で公開された新機能 Metalフレームワークの新しいダイナミックEDR（Extended Dynamic Range）レンダリングサポートを使用すると、 この記事では、この新機能をUIKitとSwiftUIの両方で使用することについて説明します。 実装 EDRはHDRと似ています。画像に保存されている輝度値を利用して、特定の画素を他の画素よりも明るくするものです。iPhoneの場合、輝度値が1より大きいと、現在設定されている画面の明るさよりも明るく表示されます。 QRコードの CIImage は、明るさの値を画面がサポートする最大値に設定して作成することができます。そして、この画像をMetalフレームワークを使ってレンダリングし、iPhoneの画面のEDR機能を利用できるようにします。 GitHubリポジトリ まずは試してみたいという方は、以下のソースコードをiPhoneで実行してみてください。 効果をよりよく確認するために iPhoneの画面の明るさを最低にし、アプリを実行してください。 シミュレータでは、EDRはサポートされていません。 https://github.com/mszpro/QR-Code-EDR?ref=mszpro.com EDR対応テスト 上記のように、EDRは画像上の特定のピクセルを、現在設定されている画面の明るさよりも明るく表示することができます。 デバイスがそれをサポートしているかどうかは、UIScreen.main.currentEDRHeadroom または view.window?.screen.currentEDRHeadroom を使用してテストする必要があります。値が>1である場合、その端末はEDRをサポートしています。 ステップ1．QRコードの作成 まず、CIFilter&#160;の一種である&#160;CIQRCodeGenerator&#160;を用いてQRを作成する。 inputMessage&#160;でQRコードの文字列内容を指定します。 ソースファイルに表示 ステップ2．画面の最大輝度を計算する iPhoneは、それぞれ最大輝度の値が異なります。また、環境やバッテリー残量によって変化することもあります。 ヘッドルーム&#160;headroom&#160;と呼ばれる、iPhoneの最大輝度値を取得する コンテンツが明るすぎるのが嫌な場合は、min で現在のヘッドルームと固定値（8など）の間の最小値を取得することができます。 ステップ3．明るさの塗りつぶしレイヤーを生成する ここで、上記で算出した明るさの最大値で塗りつぶしレイヤーを生成します。ここでの色は通常の色とは異なり、明るさを表すもので、1.0より大きくすることができますので、ご注意ください。 ソースファイルに表示 ステップ4．QR画像と輝度レイヤーを合成する ここで、生成されたQRコードに明るさの値を与えて、最終的な画像を生成します。これを行うには、QR画像&#160;qrImage&#160;をマスクとして使用し、塗りつぶしレイヤー&#160;fillImage（最大輝度のレイヤー）を切り取ります。 ソースファイルに表示 ステップ5．レンダラーをセットアップする さて、CIImage&#160;をレンダリングするレンダラーを作る必要があります。そして、レンダラーの設定で、EDRを使用するように指示します。 まず、MTKViewDelegate&#160;型に準拠した&#160;Renderer&#160;クラスを作成する。この&#160;Renderer&#160;クラスは、Metalビュー&#160;MTKView&#160;のデリゲートになりますまた、Metal で画像をレンダリングし、CIImage&#160;で作業するための適切なフレームワークをインポートする必要があります。 ソースファイルに表示 renderContext&#160;変数には、ログでのデバッグを容易にするため、レンダラーの名前を設定します。renderQueue&#160;変数には、最大値 3 の&#160;DispatchSemaphore&#160;を設定します。 ディスパッチセマフォ&#160;DispatchSemaphoreの使用 これは、プログラムが次のレンダリングを開始する前に、前のレンダリングが完了するのを待つためのツールです。 前のコマンドが終了するのを待つには、次のようにします。 _ = renderQueue.wait(timeout: DispatchTime.distantFuture)&#160;は、前のタスクが終了するまで、プログラムは次のコードの行の実行を停止します。 前のコマンドが終了したことをシステムに知らせるには、self.renderQueue.signal()&#160;を実行します。 draw&#160;関数を完成させる [&#8230;]</p>
<p>The post <a href="https://mszpro.com/ios-metal-edr">iOSアプリで画面上のQRコード部分のみ輝度を上げ（Metal, EDRレンダリング）</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>WWDC 2022で発表されたMetalの新機能では、EDRを使って画面画像の一部分だけを明るく（現在設定されている画面の明るさ以上）することができます。</p>



<p><strong>部屋の明かりを消して最小限の明るさの画面</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="811" height="1024" src="https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg.avif" alt="" class="wp-image-206" srcset="https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg-237x300.avif 237w, https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg.avif 811w, https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg-768x970.avif 768w, https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg-1216x1536.avif 1216w, https://static-assets.mszpro.com/2024/12/https-qiita-image-store.s3.ap-northeast-1.amazonaws.com-0-635330-197e8906-0111-275a-49cc-58ca63e24fd3.jpeg.avif 1284w" sizes="auto, (max-width: 811px) 100vw, 811px" /></figure>



<h1 class="wp-block-heading" id="%E8%83%8C%E6%99%AF">背景</h1>



<p>QRコード決済画面を表示する場合、通常、画面全体の輝度が高くなり（夜間に開くと気分が悪くなることがある。。。）</p>



<p>また、アプリ側で輝度の設定と復元を手動で行う必要があります。<br></p>



<h1 class="wp-block-heading" id="wwdc-2022%E3%81%A7%E5%85%AC%E9%96%8B%E3%81%95%E3%82%8C%E3%81%9F%E6%96%B0%E6%A9%9F%E8%83%BD">WWDC 2022で公開された新機能</h1>



<p>Metalフレームワークの新しいダイナミックEDR（Extended Dynamic Range）レンダリングサポートを使用すると、</p>



<ul class="wp-block-list">
<li>QRコード画像のみを最大輝度で表示（ユーザーの現在の画面輝度設定に関係なく）</li>



<li>画面の他の部分はユーザーが設定した明るさを維持</li>



<li>画面表示の明るさを変更する必要がない</li>



<li>さらに、EDRを使えば、QRコードも画面の最大輝度より明るくすることができるので、より読み取りやすくなります。</li>
</ul>



<p><strong>この記事では、この新機能をUIKitとSwiftUIの両方で使用することについて説明します。</strong><br></p>



<h1 class="wp-block-heading" id="%E5%AE%9F%E8%A3%85">実装</h1>



<p>EDRはHDRと似ています。画像に保存されている輝度値を利用して、特定の画素を他の画素よりも明るくするものです。iPhoneの場合、輝度値が1より大きいと、現在設定されている画面の明るさよりも明るく表示されます。</p>



<p>QRコードの <code>CIImage</code> は、明るさの値を画面がサポートする最大値に設定して作成することができます。<br>そして、この画像をMetalフレームワークを使ってレンダリングし、iPhoneの画面のEDR機能を利用できるようにします。<br></p>



<h2 class="wp-block-heading" id="github%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA">GitHubリポジトリ</h2>



<p>まずは試してみたいという方は、以下のソースコードをiPhoneで実行してみてください。</p>



<p>効果をよりよく確認するために iPhoneの画面の明るさを最低にし、アプリを実行してください。</p>



<p>シミュレータでは、EDRはサポートされていません。</p>



<p><a href="https://github.com/mszpro/QR-Code-EDR?ref=mszpro.com">https://github.com/mszpro/QR-Code-EDR?ref=mszpro.com</a><br></p>



<h2 class="wp-block-heading" id="edr%E5%AF%BE%E5%BF%9C%E3%83%86%E3%82%B9%E3%83%88">EDR対応テスト</h2>



<p>上記のように、EDRは画像上の特定のピクセルを、現在設定されている画面の明るさよりも明るく表示することができます。</p>



<p>デバイスがそれをサポートしているかどうかは、<code>UIScreen.main.currentEDRHeadroom</code> または <code>view.window?.screen.currentEDRHeadroom</code> を使用してテストする必要があります。値が>1である場合、その端末はEDRをサポートしています。<br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%971%EF%BC%8Eqr%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%BD%9C%E6%88%90">ステップ1．QRコードの作成</h2>



<p>まず、<code>CIFilter</code>&nbsp;の一種である&nbsp;<code>CIQRCodeGenerator</code>&nbsp;を用いてQRを作成する。</p>



<p><code>inputMessage</code>&nbsp;でQRコードの文字列内容を指定します。</p>



<pre class="wp-block-code"><code>// ここでのコードは、ステップ6で使用されます

guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else {
    return nil
}

let qrCodeContent = "testing-highlighted-qr"
let inputData = qrCodeContent.data(using: .utf8)
qrFilter.setValue(inputData, forKey: "inputMessage")
qrFilter.setValue("H", forKey: "inputCorrectionLevel")

guard let image = qrFilter.outputImage else {
    return nil
}

let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
let qrImage = image.transformed(by: sizeTransform)
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/HighlightedQRCodeView.swift?ref=mszpro.com#L23...L36">ソースファイルに表示</a><br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%972%EF%BC%8E%E7%94%BB%E9%9D%A2%E3%81%AE%E6%9C%80%E5%A4%A7%E8%BC%9D%E5%BA%A6%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B">ステップ2．画面の最大輝度を計算する</h2>



<p>iPhoneは、それぞれ最大輝度の値が異なります。<br>また、環境やバッテリー残量によって変化することもあります。</p>



<p>ヘッドルーム&nbsp;<code>headroom</code>&nbsp;と呼ばれる、iPhoneの最大輝度値を取得する</p>



<pre class="wp-block-code"><code>// ここでのコードは、ステップ6で使用されます

let screen = view.window?.screen
var headroom = CGFloat(1.0)
if #available(iOS 16.0, *) {
    headroom = screen?.currentEDRHeadroom ?? 1.0
}
</code></pre>



<p>コンテンツが明るすぎるのが嫌な場合は、<code>min</code> で現在のヘッドルームと固定値（8など）の間の最小値を取得することができます。<br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%973%EF%BC%8E%E6%98%8E%E3%82%8B%E3%81%95%E3%81%AE%E5%A1%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B">ステップ3．明るさの塗りつぶしレイヤーを生成する</h2>



<p>ここで、上記で算出した明るさの最大値で塗りつぶしレイヤーを生成します。<br>ここでの色は通常の色とは異なり、明るさを表すもので、1.0より大きくすることができますので、ご注意ください。</p>



<pre class="wp-block-code"><code>// ここでのコードは、ステップ6で使用されます

let maxRGB = headroom
guard let EDR_colorSpace = CGColorSpace(name: CGColorSpace.extendedLinearSRGB),
      let maxFillColor = CIColor(red: maxRGB, green: maxRGB, blue: maxRGB,
                                 colorSpace: EDR_colorSpace) else {
    return nil
}
let fillImage = CIImage(color: maxFillColor)
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/HighlightedQRCodeView.swift?ref=mszpro.com#L38...L45">ソースファイルに表示</a><br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%974%EF%BC%8Eqr%E7%94%BB%E5%83%8F%E3%81%A8%E8%BC%9D%E5%BA%A6%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%92%E5%90%88%E6%88%90%E3%81%99%E3%82%8B">ステップ4．QR画像と輝度レイヤーを合成する</h2>



<p>ここで、生成されたQRコードに明るさの値を与えて、最終的な画像を生成します。<br>これを行うには、QR画像&nbsp;<code>qrImage</code>&nbsp;をマスクとして使用し、塗りつぶしレイヤー&nbsp;<code>fillImage</code>（最大輝度のレイヤー）を切り取ります。</p>



<pre class="wp-block-code"><code>// ここでのコードは、ステップ6で使用されます

let maskFilter = CIFilter.blendWithMask()

maskFilter.maskImage = qrImage
maskFilter.inputImage = fillImage

guard let combinedImage = maskFilter.outputImage else {
    return nil
}

return combinedImage.cropped(to: CGRect(x: 0, y: 0,
                                width: 300 * scaleFactor,
                                height: 300 * scaleFactor))
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/HighlightedQRCodeView.swift?ref=mszpro.com#L47...L59">ソースファイルに表示</a><br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%975%EF%BC%8E%E3%83%AC%E3%83%B3%E3%83%80%E3%83%A9%E3%83%BC%E3%82%92%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%81%99%E3%82%8B">ステップ5．レンダラーをセットアップする</h2>



<p>さて、<code>CIImage</code>&nbsp;をレンダリングするレンダラーを作る必要があります。<br>そして、レンダラーの設定で、EDRを使用するように指示します。</p>



<p>まず、<code>MTKViewDelegate</code>&nbsp;型に準拠した&nbsp;<code>Renderer</code>&nbsp;クラスを作成する。<br>この&nbsp;<code>Renderer</code>&nbsp;クラスは、Metalビュー&nbsp;<code>MTKView</code>&nbsp;のデリゲートになります<br>また、Metal で画像をレンダリングし、<code>CIImage</code>&nbsp;で作業するための適切なフレームワークをインポートする必要があります。</p>



<pre class="wp-block-code"><code>import Metal
import MetalKit
import CoreImage

class Renderer: NSObject, MTKViewDelegate, ObservableObject {
    
    let imageProvider: (_ contentScaleFactor: CGFloat, _ headroom: CGFloat) -&gt; CIImage? // 画像データを提供する呼び出し側デリゲート関数
    
    public let device: MTLDevice? = MTLCreateSystemDefaultDevice()
    
    let commandQueue: MTLCommandQueue?
    let renderContext: CIContext? // 名前、キャッシュ環境設定、低電力設定の設定
    let renderQueue = DispatchSemaphore(value: 3) // 新しいフレームを描画する前に、前のレンダリングが完了するのを待つために使用される
    
    init(imageProvider: @escaping (_ contentScaleFactor: CGFloat, _ headroom: CGFloat) -&gt; CIImage?) {
        self.imageProvider = imageProvider
        self.commandQueue = self.device?.makeCommandQueue()
        if let commandQueue {
            self.renderContext = CIContext(mtlCommandQueue: commandQueue,
                                       options: &#91;.name: "QR-Code-Renderer",
                                                 .cacheIntermediates: true,
                                                 .allowLowPower: true])
        } else {
            self.renderContext = nil
        }
        super.init()
    }
    
    func draw(in view: MTKView) {
        // ToDo
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        // 描画可能なサイズや向きの変更に対応する。
    }
    
}
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Model/Renderer.swift?ref=mszpro.com">ソースファイルに表示</a></p>



<p><code>renderContext</code>&nbsp;変数には、ログでのデバッグを容易にするため、レンダラーの名前を設定します。<br><code>renderQueue</code>&nbsp;変数には、最大値 3 の&nbsp;<code>DispatchSemaphore</code>&nbsp;を設定します。</p>



<h3 class="wp-block-heading" id="%E3%83%87%E3%82%A3%E3%82%B9%E3%83%91%E3%83%83%E3%83%81%E3%82%BB%E3%83%9E%E3%83%95%E3%82%A9-dispatchsemaphore%E3%81%AE%E4%BD%BF%E7%94%A8">ディスパッチセマフォ&nbsp;<code>DispatchSemaphore</code>の使用</h3>



<p>これは、プログラムが次のレンダリングを開始する前に、前のレンダリングが完了するのを待つためのツールです。</p>



<p>前のコマンドが終了するのを待つには、次のようにします。</p>



<p><code>_ = renderQueue.wait(timeout: DispatchTime.distantFuture)</code>&nbsp;は、前のタスクが終了するまで、プログラムは次のコードの行の実行を停止します。</p>



<p>前のコマンドが終了したことをシステムに知らせるには、<code>self.renderQueue.signal()</code>&nbsp;を実行します。</p>



<h3 class="wp-block-heading" id="draw-%E9%96%A2%E6%95%B0%E3%82%92%E5%AE%8C%E6%88%90%E3%81%95%E3%81%9B%E3%82%8B"><code>draw</code>&nbsp;関数を完成させる</h3>



<p>次に、コンテンツを描画する&nbsp;<code>func draw(in view: MTKView)</code>&nbsp;関数に取り組みます。</p>



<pre class="wp-block-code"><code>class Renderer: NSObject, MTKViewDelegate, ObservableObject {
    
    // ... 上記のコードは、変数とinit関数です。 //
    
    func draw(in view: MTKView) {
        
        guard let commandQueue else { return }

        // wait for previous render to complete
        _ = renderQueue.wait(timeout: DispatchTime.distantFuture)
        
        if let commandBuffer = commandQueue.makeCommandBuffer() {
            
            // コマンドが完了したら、次のフレームをレンダリングできるようにキューに通知する。
            commandBuffer.addCompletedHandler { (_ commandBuffer)-&gt; Swift.Void in
                self.renderQueue.signal()
            }
            
            if let drawable = view.currentDrawable {
                
                let drawSize = view.drawableSize
                let contentScaleFactor = view.contentScaleFactor
                let destination = CIRenderDestination(width: Int(drawSize.width),
                                                      height: Int(drawSize.height),
                                                      pixelFormat: view.colorPixelFormat,
                                                      commandBuffer: commandBuffer,
                                                      mtlTextureProvider: { () -&gt; MTLTexture in
                    return drawable.texture
                })
                
                // サポートされる最大EDR値（ヘッドルーム）を計算する
                var headroom = CGFloat(1.0)
                if #available(iOS 16.0, *) {
                    headroom = view.window?.screen.currentEDRHeadroom ?? 1.0
                }
                
                // デリゲート関数から表示するCI画像を取得します。
                guard var image = self.imageProvider(contentScaleFactor, headroom) else {
                    return
                }

                // ビューの可視領域で画像を中央に配置します。
                let iRect = image.extent
                let backBounds = CGRect(x: 0,
                                        y: 0,
                                        width: drawSize.width,
                                        height: drawSize.height)
                let shiftX = round((backBounds.size.width + iRect.origin.x - iRect.size.width) * 0.5)
                let shiftY = round((backBounds.size.height + iRect.origin.y - iRect.size.height) * 0.5)
                image = image.transformed(by: CGAffineTransform(translationX: shiftX, y: shiftY))
                
                // 画像が透明の場合、背景を指定する
                image = image.composited(over: .gray)
                
                // テクスチャーデスティネーションにレンダリングするタスクを開始します。
                guard let renderContext else { return }
                _ = try? renderContext.startTask(toRender: image, from: backBounds,
                                                  to: destination, at: CGPoint.zero)
                
                // レンダリング結果を表示し、レンダリングタスクをコミットする
                commandBuffer.present(drawable)
                commandBuffer.commit()
            }
        }
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        // 描画可能なサイズや向きの変更に対応する。
    }
    
}
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Model/Renderer.swift?ref=mszpro.com">ソースファイルに表示</a></p>



<p>上記のコードでは、<br>まず <code>renderQueue.wait</code> 関数を使用して、前のレンダリングが完了するのを待ちます。<br>次に、コマンドバッファを取得し、描画のサイズを指定します。<br>HDRで提供できる最大の明るさ（ヘッドルーム）を計算します。<br>そして、CI画像オブジェクトをメタルビュー内でレンダリングし、レンダリング画像を中央に配置する。<br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%976%EF%BC%8E%E3%83%AC%E3%83%B3%E3%83%80%E3%83%A9%E3%83%BC%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%E3%81%99%E3%82%8B">ステップ6．レンダラーを初期化する</h2>



<p>さて、QRコードと輝度値を含む生成したCIImage（手順1～手順4のコード）と、Rendererクラス（手順5）を元に、レンダラーを初期化します。</p>



<pre class="wp-block-code"><code>let renderer = Renderer(imageProvider: { (scaleFactor: CGFloat, headroom: CGFloat) -&gt; CIImage? in
    
    // QRコード画像を生成する
    guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else {
        return nil
    }
    
    let qrCodeContent = "testing-highlighted-qr"
    let inputData = qrCodeContent.data(using: .utf8)
    qrFilter.setValue(inputData, forKey: "inputMessage")
    qrFilter.setValue("H", forKey: "inputCorrectionLevel")
    
    guard let image = qrFilter.outputImage else {
        return nil
    }
    
    let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
    let qrImage = image.transformed(by: sizeTransform)
    
    // 空白の塗りつぶし画像を生成する
    let maxRGB = headroom
    let maxFillColor = CIColor(red: maxRGB, green: maxRGB, blue: maxRGB,
                               colorSpace: CGColorSpace(name: CGColorSpace.extendedLinearSRGB)!)!
    let fillImage = CIImage(color: maxFillColor)
    
    // マスクフィルターを使って、最終的なQRコード画像を作成します。
    let maskFilter = CIFilter.blendWithMask()
    maskFilter.maskImage = qrImage
    maskFilter.inputImage = fillImage
    
    // ハイライトレイヤーとQR画像を合成する
    guard let combinedImage = maskFilter.outputImage else {
        return nil
    }
    return combinedImage.cropped(to: CGRect(x: 0, y: 0,
                                            width: 512.0 * scaleFactor,
                                            height: 384.0 * scaleFactor))
})
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/HighlightedQRCodeView.swift?ref=mszpro.com#L20...L59">ソースファイルに表示</a><br></p>



<h2 class="wp-block-heading" id="%E3%82%B9%E3%83%86%E3%83%83%E3%83%977%EF%BC%8E%E3%83%A1%E3%82%BF%E3%83%AB%E3%83%93%E3%83%A5%E3%83%BC%E3%81%A7qr%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B">ステップ7．メタルビューでQRコードを表示する</h2>



<p>それでは、メタルビューにQRコードを表示してみましょう。</p>



<pre class="wp-block-code"><code>@StateObject var renderer: Renderer // 変数

let metalView = MTKView(frame: .zero, device: renderer.device)

// MetalKitを通じてCore Animationに、ビューの再描画頻度を提案する。
metalView.preferredFramesPerSecond = 10

// Core Imageがメタルコンピュートパイプラインを使用してビューにレンダリングできるようにします。
metalView.framebufferOnly = false
metalView.delegate = renderer

if let layer = metalView.layer as? CAMetalLayer {
    // SDRより大きな値をサポートする色空間でEDRを有効にする。
    if #available(iOS 16.0, *) {
        layer.wantsExtendedDynamicRangeContent = true
    }
    layer.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3)
    // レンダービューがEDRのピクセル値をサポートしていることを確認する。
    metalView.colorPixelFormat = MTLPixelFormat.rgba16Float
}
</code></pre>



<p>上記のコードでは、<br>新しいMetalビュー MTKView を初期化し、<br>リフレッシュレート（フレーム毎秒）を10に設定し、<br>Renderer インスタンスに delegate を設定し、<br>EDR（Extended Dynamic Range）を使用するように設定しています。</p>



<p>UIKitを使用している場合は、このビューをそのままUIKitのビューに追加することができます <code>view.addSubview(metalView)</code><br></p>



<h2 class="wp-block-heading" id="swiftui%E4%BA%92%E6%8F%9B%E3%81%AE%E3%83%A1%E3%82%BF%E3%83%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%93%E3%83%A5%E3%83%BC%E3%82%92%E4%BD%9C%E3%82%8B">SwiftUI互換のメタルレンダリングビューを作る</h2>



<p>アダプターを使うことで、SwiftUIと互換性のあるメタルビューを作ることもできます。</p>



<pre class="wp-block-code"><code>import SwiftUI
import MetalKit

struct MetalView: ViewRepresentable {
    
    @StateObject var renderer: Renderer
    /// - Tag: MakeView
    func makeView(context: Context) -&gt; MTKView {
        let view = MTKView(frame: .zero, device: renderer.device)

        // MetalKitを通じてCore Animationに、ビューの再描画頻度を提案する。
        view.preferredFramesPerSecond = 10

        // Core Imageがメタルコンピュートパイプラインを使用してビューにレンダリングできるようにします。
        view.framebufferOnly = false
        view.delegate = renderer

        if let layer = view.layer as? CAMetalLayer {
            // SDRより大きな値をサポートする色空間でEDRを有効にする。
            if #available(iOS 16.0, *) {
                layer.wantsExtendedDynamicRangeContent = true
            }
            layer.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3)
            // レンダービューがEDRのピクセル値をサポートしていることを確認する。
            view.colorPixelFormat = MTLPixelFormat.rgba16Float
        }
        return view
    }
    
    func updateView(_ view: MTKView, context: Context) {
        configure(view: view, using: renderer)
    }
    
    private func configure(view: MTKView, using renderer: Renderer) {
        view.delegate = renderer
    }
}
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/MetalView_SwiftUICompatible.swift?ref=mszpro.com">ソースファイルに表示</a><br></p>



<h2 class="wp-block-heading" id="swiftui%E3%83%93%E3%83%A5%E3%83%BC%E3%81%A7%E3%83%AC%E3%83%B3%E3%83%80%E3%83%A9%E3%83%BC%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B">SwiftUIビューでレンダラーを使用する</h2>



<p>さて、初期化されたレンダラーのインスタンスと、上で作成した互換性のあるビューアダプターの両方を使用して、ハイライトされたQRコード画像を表示する単一のSwiftUIビューを作成することができます。</p>



<pre class="wp-block-code"><code>import SwiftUI
import CoreImage.CIFilterBuiltins

/// - Tag: ContentView
struct ContentView: View {
    var body: some View {
        // 独自のレンダラーを持つメタルビューを作成します。
        let renderer = Renderer(imageProvider: { (scaleFactor: CGFloat, headroom: CGFloat) -&gt; CIImage? in
            
            // QRコード画像を生成する
            guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else {
                return nil
            }
            
            let qrCodeContent = "testing-highlighted-qr"
            let inputData = qrCodeContent.data(using: .utf8)
            qrFilter.setValue(inputData, forKey: "inputMessage")
            qrFilter.setValue("H", forKey: "inputCorrectionLevel")
            
            guard let image = qrFilter.outputImage else {
                return nil
            }
            
            let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
            let qrImage = image.transformed(by: sizeTransform)
            
            // 空白の塗りつぶし画像を生成する
            let maxRGB = headroom
            let maxFillColor = CIColor(red: maxRGB, green: maxRGB, blue: maxRGB,
                                       colorSpace: CGColorSpace(name: CGColorSpace.extendedLinearSRGB)!)!
            let fillImage = CIImage(color: maxFillColor)
            
            // マスクフィルターを使って、最終的なQRコード画像を作成します。
            let maskFilter = CIFilter.blendWithMask()
            maskFilter.maskImage = qrImage
            maskFilter.inputImage = fillImage
            
            // ハイライトレイヤーとQR画像を合成する
            guard let combinedImage = maskFilter.outputImage else {
                return nil
            }
            return combinedImage.cropped(to: CGRect(x: 0, y: 0,
                                            width: 512.0 * scaleFactor,
                                            height: 384.0 * scaleFactor))
        })

        MetalView(renderer: renderer)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
</code></pre>



<p><a href="https://github.com/mszpro/QR-Code-EDR/blob/main/EDR%20QR/Views/HighlightedQRCodeView.swift?ref=mszpro.com">ソースファイルに表示</a></p>



<p>ここで、iPhoneで実行すると、QRコードだけがハイライトされるのが見えます。</p>



<p>また、異なる塗りつぶしレイヤーを生成することで、画像の異なる部分を強調することができます。また、明るさの値を1より小さくすることで、特定の部分をより暗くすることができます。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>お読みいただきありがとうございました。</p>



<p><a href="https://twitter.com/MszPro?ref=mszpro.com">☺️ Twitter @MszPro</a><br><a href="https://sns.mszpro.com/@me?ref=mszpro.com">🐘 Mastodon @me@mszpro.com</a></p>



<figure class="wp-block-image"><img decoding="async" src="https://qiita-user-contents.imgix.net/https%3A%2F%2Fcdn.mszmagic.com%2Fstatic-web-content%2FAppClipImage_small.png?ixlib=rb-4.0.0&amp;auto=format&amp;gif-q=60&amp;q=75&amp;s=a4fb7ab3d958113fcae3f48ae6b179cd" alt=""/></figure><p>The post <a href="https://mszpro.com/ios-metal-edr">iOSアプリで画面上のQRコード部分のみ輝度を上げ（Metal, EDRレンダリング）</a> first appeared on <a href="https://mszpro.com">MszPro・株式会社Smartソフト</a>.</p>]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching 15/112 queries in 0.033 seconds using Disk

Served from: mszpro.com @ 2025-07-04 20:46:56 by W3 Total Cache
-->