<?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/"
	xmlns:series="https://publishpress.com/"
	>

<channel>
	<title>IOS Archives - Tomoshare</title>
	<atom:link href="https://blog.tomosia.com.vn/danh-muc/ios/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.tomosia.com.vn/danh-muc/ios/</link>
	<description>Kênh chia sẻ kiến thức Tomosia Việt Nam</description>
	<lastBuildDate>Wed, 06 Mar 2024 01:34:29 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://blog.tomosia.com.vn/wp-content/uploads/2023/09/cropped-icon-32x32.png</url>
	<title>IOS Archives - Tomoshare</title>
	<link>https://blog.tomosia.com.vn/danh-muc/ios/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</title>
		<link>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/</link>
					<comments>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/#comments</comments>
		
		<dc:creator><![CDATA[admin_tomosia]]></dc:creator>
		<pubDate>Tue, 05 Mar 2024 06:16:18 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=3196</guid>

					<description><![CDATA[<p>Trong bài viết này, tôi sẽ hướng dẫn cách thiết lập và triển khai ứng dụng Flutter của&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/">Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Trong bài viết này, tôi sẽ hướng dẫn cách thiết lập và triển khai ứng dụng Flutter của bạn và phân phối ứng dụng tự động với sự trợ giúp của fastlane.</p>



<p><strong>Fastlane là gì? </strong></p>



<p>Fastlane là một công cụ phân phối ứng dụng (CD) mã nguồn mở giúp tự động hóa việc xây dựng và triển khai ứng dụng Android và iOS lên Firebase,  Play Store hoặc App Store. Bạn có thể tùy chỉnh các lane theo môi trường DEV, STAGING, PROD theo ý muốn và chúng cũng giúp bạn tự động hóa quy trình ký mã cho cả hai nền tảng.</p>



<p>Cấu hình cho fastlane cho flutter thực chất là cấu hình riêng cho 2 môi trường iOS và Android trong project. Cấu hình riêng này hoàn toàn có thể dùng cho môi trường native iOS hoặc Android.<br><br>Tôi sẽ chia làm 3 phần<br>Phần 1: Config fastlane Firebase app distribution cho iOS<br>Phần 2: Config fastlane Firebase app distribution cho Android<br>Phần 3: Config fastlane Testflight iOS và Google Playstore Android<br><br>Hãy cùng bắt đầu với phần 1: Config fastlane Firebase app distribution cho iOS</p>



<p><strong>Bước 1: Cài đặt fastlane</strong></p>



<p>Bắt đầu cài đặt đơn giản qua Homebrew (macOS). Bạn có thể tham khảo tại tại <a href="https://docs.fastlane.tools/getting-started/ios/setup/">fastlane doc</a></p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="brew install fastlane" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">brew install fastlane</span></span></code></pre></div>



<p><strong>Bước 2: Khởi tạo fastlane trong project iOS</strong></p>



<p>trỏ tới thư mục iOS trong dự án flutter của bạn thông qua terminal </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="cd ios" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">cd ios</span></span></code></pre></div>



<p>khởi tạo fastlane </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane init" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane init</span></span></code></pre></div>



<p>Lúc này sẽ có 4 lựa chọn để fastlane tạo sẵn cho bạn config mặc định. Để đơn giản hay bắt đầu với lựa chọn 4:  Manual setup </p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="648" height="399" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25.png" alt="" class="wp-image-3199" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25.png 648w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25-300x185.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.33.25-380x234.png 380w" sizes="(max-width: 648px) 100vw, 648px" /></figure>



<p>Tiếp tục và Enter cho đến khi hoàn thành<br><br>Lúc này kiểm tra trong thư mục ios sẽ sinh ra thu mục fastlane và các file config sẵn, chúng ta sẽ chú ý tới Fastfile, nơi config các lệnh tự động.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="398" height="383" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29.png" alt="" class="wp-image-3200" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29.png 398w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29-300x289.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.39.29-380x366.png 380w" sizes="(max-width: 398px) 100vw, 398px" /></figure>



<p>Mở file Fastfile bạn sẽ thấy config mặc định lane: custom_lane<br>chúng ta sẽ thêm 1 dòng printf(&#8220;Hello fastlane&#8221;) để test</p>



<figure class="wp-block-image size-full"><img decoding="async" width="630" height="399" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14.png" alt="" class="wp-image-3201" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14.png 630w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14-300x190.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.43.14-380x241.png 380w" sizes="(max-width: 630px) 100vw, 630px" /></figure>



<p>Giờ hãy cùng test qua terminal để xem fastlane nó chạy như nào:<br>chú ý vẫn tại thư mục ios trong project </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane custom_lane" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane custom_lane</span></span></code></pre></div>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="647" height="414" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01.png" alt="" class="wp-image-3202" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01.png 647w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01-300x192.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-09.45.01-380x243.png 380w" sizes="auto, (max-width: 647px) 100vw, 647px" /></figure>



<p><em>Hello fastlane</em> đã được in ra  🎉</p>



<ul class="wp-block-list">
<li>Fastfile được viết bằng Ruby</li>
</ul>



<ul class="wp-block-list">
<li>lane: custom_lane nơi bạn gom chạy các task vụ chung lại với nhau. Bạn có thể tìm hiểu thêm tại <a href="https://docs.fastlane.tools/advanced/lanes/">doc</a><br><br>Giờ chúng ta cùng nhau config chi tiết lane: DEV mục tiêu là đẩy bản build môi trường DEV lên firebase app distribution.</li>
</ul>



<p><strong>Bước 3: Import certificate và provisioning profile </strong></p>



<ul class="wp-block-list">
<li>Để build được file ipa và đẩy lên firebase distribution cần certificate và provisioning được setup và cấu hình qua tài khoản apple developer. Trong bài viết tôi không đi sâu vào vấn đề tạo certificate cũng như provisioning tuy nhiên bạn có thể tìm hiểu thêm tại <a href="https://developer.apple.com/help/account/manage-profiles/create-a-development-provisioning-profile/">đây</a></li>



<li>Tới bước này mặc định bạn đã có certificate dạng dev_ios_distribution.p12 (với password) và provisioning dạng ios_app_name_dev_adhoc.mobileprovision cho môi trường DEV<br><br>Chúng ta có 2 cách<br>&#8211; Cách 1: là đưa lên git private mục đích bảo mật dành cho những thành viên có quyền mới truy cập vào build deploy được. Với cách này chúng ta dùng fastlane action match để get từ github private và import. Chi tiết tại <a href="https://docs.fastlane.tools/actions/match/#match">fastlane action match</a><br>&#8211; Cách 2: đưa thẳng file certificate và provisioning vào cùng thư mục để dễ dàng truy cập. Tôi sẽ đi sâu vào cách này ví nó đơn giản. <br>Tạo 2 thư mục certs và profile để save local cùng project ios</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="479" height="384" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31.png" alt="" class="wp-image-3204" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31.png 479w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31-300x241.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.12.31-380x305.png 380w" sizes="auto, (max-width: 479px) 100vw, 479px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="596" height="386" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1.png" alt="" class="wp-image-3206" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1.png 596w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1-300x194.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.13.46-1-380x246.png 380w" sizes="auto, (max-width: 596px) 100vw, 596px" /></figure>



<p><br>Bắt đầu config lane DEV trong Fastfile</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="655" height="423" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20.png" alt="" class="wp-image-3208" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20.png 655w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20-300x194.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-10.34.20-380x245.png 380w" sizes="auto, (max-width: 655px) 100vw, 655px" /></figure>



<p>Thực hiện fastlane action <a href="https://docs.fastlane.tools/actions/import_certificate/#import_certificate">import_certificate</a> <br>&#8211;<strong>keychain_name</strong> :  kiểm tra xem certificate của bạn được import trong keychain nào, open Keychain Access trong Macos và tìm. Trong trường hợp của máy tôi, certificate <strong>iPhone Distribution xxx COMPANY LIMITTED (yyyyy) </strong>có trong keychain <strong>login</strong><br>&#8211;<strong>certificate_password</strong>: password <strong>certificate_distribution.p12 </strong>của bạn<br>&#8211;<strong>keychain_password</strong>: password vào keychain của bạn.<br><br>Thực hiện tiếp fastlane action <a href="https://docs.fastlane.tools/actions/install_provisioning_profile/#install_provisioning_profile">install_provisioning_profile</a><br>Trong trường hợp chạy máy ảo hoặc bạn muốn tách biệt bạn có thể dùng fastlane action <a href="https://docs.fastlane.tools/actions/create_keychain/#create_keychain">create_keychain</a> để tạo riêng keychain cho bạn.<br><br>Tiếp tục với action <a href="https://docs.fastlane.tools/actions/build_ios_app/#build_ios_app">build_ios_app</a> thực hiện export file ipa. Cần chú ý vài điểm sau<br>&#8211;<strong>scheme</strong> và <strong>configuration</strong>: đúng như bạn đã config trong xcode<br>&#8211;<strong>output_directory</strong>: đường dẫn thư mục chứ file ipa sau khi export thành công<br>&#8211;<strong>export_method</strong>: ad-hoc<br>&#8211;<strong>codesigning_identity</strong>: tên chính sác của provisioning profile<br>&#8211;<strong>provisionningProfile</strong>: chính xác tên bundle và tên file provisioning<br><br>Sau khi config xong test thử lệnh </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="fastlane DEV" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">fastlane </span><span style="color: #BF9EEE">DEV</span></span></code></pre></div>



<p>Vài lỗi có thể gặp phải:</p>



<ul class="wp-block-list">
<li>import certificate error: kiểm tra lại tên file và đường dẫn. Có thể lấy đường dẫn trực tiếp để test kỹ.</li>



<li>build_ios_app error: thêm debug: true để check kỹ lỗi gặp phải.</li>
</ul>



<p>Sau khi build thành công bạn sẽ thấy thư mục build/dev có file APP-DEV.ipa như hình</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="700" height="431" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21.png" alt="" class="wp-image-3209" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21.png 700w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21-300x185.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.02.21-380x234.png 380w" sizes="auto, (max-width: 700px) 100vw, 700px" /></figure>



<p>Ngoài ra còn có file .dSYM.zip .File này mục đích để giải mã lỗi ngược dùng cho firebase crashlytics. Trường hợp bạn export không có file dSYM.zip hãy kiểm tra config trong Debug Information Format chọn DWARK with dSYM File.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1002" height="572" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25.png" alt="" class="wp-image-3210" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25.png 1002w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-300x171.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-768x438.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-380x217.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.09.25-800x457.png 800w" sizes="auto, (max-width: 1002px) 100vw, 1002px" /></figure>



<p><strong>4. Cấu hình upload Firebase distribution</strong><br>Giả định bạn đã setup thành công firebase và kết nối với project flutter. Chi tiết config firebase bạn có thể xem tại <a href="https://firebase.google.com/docs/flutter/setup?platform=ios">đây</a>. Chúng ta sẽ tiếp cụ với: </p>



<ul class="wp-block-list">
<li>Tìm App ID thông qua Firebase Console</li>



<li>Để phân phối ứng dụng thông qua Fastlane, bạn cần một <a href="https://firebase.google.com/docs/cli#install-cli-mac-linux">Firebase CLI token</a>. Chạy lệnh sau trong terminal để đăng nhập và tạo token:</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Ruby</span><span role="button" tabindex="0" data-code="firebase login:ci" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">firebase login</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4">ci</span></span></code></pre></div>



<p>Sau khi đăng nhập thành công, bạn sẽ nhận được một token. Hãy lưu trữ token này một cách an toàn.</p>



<p>Tiếp tục mở Fastfile và thêm config fastlane action <a href="https://docs.fastlane.tools/getting-started/ios/beta-deployment/#beta-testing-services">firebase_app_distriution</a> và <a href="https://docs.fastlane.tools/actions/upload_symbols_to_crashlytics/#upload_symbols_to_crashlytics">upload_symbols_to_crashlytics</a> </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="792" height="689" src="http://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04.png" alt="" class="wp-image-3211" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04.png 792w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-300x261.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-768x668.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2024/03/Screenshot-2024-03-05-at-11.15.04-380x331.png 380w" sizes="auto, (max-width: 792px) 100vw, 792px" /></figure>



<p>&#8211;<strong>groups</strong>: tên group test config trong firebase app distribution<br>&#8211;<strong>testers</strong>: list email chỉ định tới các tester đã add trong firebase app distribution<br>&#8211;<strong>upload_symbols_to_crashlytics</strong>: nếu bạn không sử dụng Firebase crashlytics hãy bỏ nó đi. <br><br>Các lỗi gặp phải:<br>-sai đường dẫn hoặc tên file ipa<br>-hãy đảm bảo bạn đã kích hoạt app distribution của ios trên firebase console<br>-đảm bảo version name, version code khác nhau mỗi lần upload.<br><br>Tới đây bạn đã hoàn thành việc config fastlane build Firebase app distribution cho môi trường iOS. <br>Hãy cùng đón đợi tiếp phần 2 config cho môi trường Android.</p>
<p>The post <a href="https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/">Fastlane Flutter App Distribution (part 1: Firebase app distribution iOS)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/fastlane-flutter-app-distribution-part-1-firebase-app-distribution-ios/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Custom View Modifiers trong SwiftUI</title>
		<link>https://blog.tomosia.com.vn/custom-view-modifiers-trong-swiftui/</link>
					<comments>https://blog.tomosia.com.vn/custom-view-modifiers-trong-swiftui/#comments</comments>
		
		<dc:creator><![CDATA[Thanh Nguyen]]></dc:creator>
		<pubDate>Thu, 21 Dec 2023 02:02:30 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[ios]]></category>
		<category><![CDATA[swiftui]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=2616</guid>

					<description><![CDATA[<p>What are “View Modifiers”? Trong SwiftUI, view modifiers là một cách để thay đổi hoặc thiết lập hành&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/custom-view-modifiers-trong-swiftui/">Custom View Modifiers trong SwiftUI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h5 class="wp-block-heading has-cyan-bluish-gray-color has-text-color has-link-color wp-elements-141539c128789e447cbcf37c3824adf3" id="4988"><span id="what-are-view-modifiers">What are “View Modifiers”?</span></h5>



<pre class="wp-block-preformatted">Trong SwiftUI, view modifiers là một cách để thay đổi hoặc thiết lập hành vi cho một view cụ thể. Chúng là các cấu trúc nhẹ nhàng cho phép bạn thay đổi giao diện, hành vi hoặc bố cục của một view. Các bộ điều chỉnh view được áp dụng bằng cách sử dụng cú pháp dấu chấm và có thể được nối với nhau để thực hiện những sửa đổi phức tạp hơn.

View modifiers không chỉ cho phép bạn thay đổi hình dạng và hành vi của một view, mà còn cho phép bạn tạo ra những thay đổi phức tạp bằng cách nối chúng lại với nhau. Với SwiftUI, bạn có thể thay đổi font, màu sắc, khoảng trống, căn chỉnh, và nhiều tính năng khác của view chỉ bằng cú pháp đơn giản.

Ngoài ra, SwiftUI cung cấp các view modifier tích hợp sẵn, giúp bạn tiết kiệm thời gian và công sức trong việc tạo ra giao diện người dùng đẹp và hiệu quả. Hơn nữa, bạn cũng có thể tạo ra những bộ điều chỉnh tùy chỉnh phù hợp với yêu cầu cụ thể của ứng dụng của mình.
</pre>



<p>Trong SwiftUI, view modifiers là một cách hữu dụng để có thể đóng gói và tái sử dụng các hành vi và styles cho các view. </p>



<p>Bạn có thể làm điều này bằng cách tạo một struct <strong>ViewModifier</strong> mới với giao thức <strong>ViewModifier</strong>. Ở đây, mình tạo một struct <strong>CustomViewModifier</strong> và tuân thủ nó vào giao thức <strong>ViewModifier</strong>.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4296875px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="struct CustomViewModifier: ViewModifier {

    func body(content: Content) -&gt; some View {
        content
    }

}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">CustomViewModifier</span><span style="color: #F6F6F4">: ViewModifier {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">body</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">content</span><span style="color: #F6F6F4">: Content) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        content</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<p>Bây giờ, bạn có thể thêm các modifier vào content này theo ý muốn. Mình sẽ thêm một số view modifier tích hợp sẵn để thêm border.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="struct CustomViewModifier: ViewModifier {

    func body(content: Content) -&gt; some View {
        content
            .padding()
            .cornerRadius(10)
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.black, lineWidth: 2)
            )
    }
}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">CustomViewModifier</span><span style="color: #F6F6F4">: ViewModifier {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">body</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">content</span><span style="color: #F6F6F4">: Content) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        content</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">padding</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">overlay</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">RoundedRectangle</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">stroke</span><span style="color: #F6F6F4">(Color.black, </span><span style="color: #97E1F1">lineWidth</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            )</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<p>Chúng ta có thể tùy chỉnh các thuộc tính của bộ điều chỉnh view như corner radius, border width, v.v. </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="struct CustomViewModifier: ViewModifier {
    
    var cornerRadius: CGFloat
    var borderColor: Color
    var borderWidth: CGFloat

    func body(content: Content) -&gt; some View {
        content
            .padding()
            .cornerRadius(cornerRadius)
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .stroke(borderColor, lineWidth: borderWidth)
            )
    }
}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">CustomViewModifier</span><span style="color: #F6F6F4">: ViewModifier {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> cornerRadius: CGFloat</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> borderColor: Color</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> borderWidth: CGFloat</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">body</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">content</span><span style="color: #F6F6F4">: Content) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        content</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">padding</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">(cornerRadius)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">overlay</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">RoundedRectangle</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: cornerRadius)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">stroke</span><span style="color: #F6F6F4">(borderColor, </span><span style="color: #97E1F1">lineWidth</span><span style="color: #F6F6F4">: borderWidth)</span></span>
<span class="line"><span style="color: #F6F6F4">            )</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<p>Chúng ta có thể gọi trực tiếp custom modifier này hoặc bạn có thể đặt một tên cho nó bằng cách tạo extension từ <strong>View</strong>.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:8.4296875px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code=".modifier(CustomViewModifier(cornerRadius: 10, 
                             borderColor: .black, 
                             borderWidth: 2)) " style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">modifier</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">CustomViewModifier</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">, </span></span>
<span class="line"><span style="color: #F6F6F4">                             </span><span style="color: #97E1F1">borderColor</span><span style="color: #F6F6F4">: .black, </span></span>
<span class="line"><span style="color: #F6F6F4">                             </span><span style="color: #97E1F1">borderWidth</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">)) </span></span></code></pre></div>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="extension View{
    //--Chúng ta có thể đặt tên cho custom modifer
    func addBorder(cornerRadius: CGFloat, 
                   borderColor: Color,
                   borderWidth: CGFloat) -&gt; some View{
        self.modifier(CustomViewModifier(cornerRadius: cornerRadius, 
                                         borderColor: borderColor,
                                         borderWidth: borderWidth))
    }
}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">extension</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">View</span><span style="color: #F6F6F4">{</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #7B7F8B">//--Chúng ta có thể đặt tên cho custom modifer</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">addBorder</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">cornerRadius</span><span style="color: #F6F6F4">: CGFloat, </span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #62E884; font-style: italic">borderColor</span><span style="color: #F6F6F4">: Color,</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #62E884; font-style: italic">borderWidth</span><span style="color: #F6F6F4">: CGFloat) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View{</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">modifier</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">CustomViewModifier</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: cornerRadius, </span></span>
<span class="line"><span style="color: #F6F6F4">                                         </span><span style="color: #97E1F1">borderColor</span><span style="color: #F6F6F4">: borderColor,</span></span>
<span class="line"><span style="color: #F6F6F4">                                         </span><span style="color: #97E1F1">borderWidth</span><span style="color: #F6F6F4">: borderWidth))</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<p>Bây giờ hãy sử dụng modifier này trong view của bạn.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="struct ContentView: View {
    var body: some View {
        VStack {
            Text(&quot;Hello, world!&quot;)
                .addBorder(cornerRadius: 8, 
                           borderColor: Color.blue, 
                           borderWidth: 2)
        }
    }
}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        VStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Hello, world!</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">addBorder</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">8</span><span style="color: #F6F6F4">, </span></span>
<span class="line"><span style="color: #F6F6F4">                           </span><span style="color: #97E1F1">borderColor</span><span style="color: #F6F6F4">: Color.blue, </span></span>
<span class="line"><span style="color: #F6F6F4">                           </span><span style="color: #97E1F1">borderWidth</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">2</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<p>Kết quả chúng ta sẽ được như hình bên dưới: </p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="369" height="231" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/1_9-VoC_PsPrFMjSVm3r3aPg-edited.webp" alt="" class="wp-image-2687" style="width:405px;height:auto" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/1_9-VoC_PsPrFMjSVm3r3aPg-edited.webp 369w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/1_9-VoC_PsPrFMjSVm3r3aPg-edited-300x188.webp 300w" sizes="auto, (max-width: 369px) 100vw, 369px" /></figure>
</div>


<p>Thêm một ví dụ custom modifier với Linear Gradient: Tạo một view modifier có background là linear gradient và có thể blur.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:16.859375px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#ebebe6">Swift</span><span role="button" tabindex="0" data-code="struct BlurWithLinearGradient: ViewModifier {
    let isBlur: Bool
    let cornerRadius: CGFloat
    
    func body(content: Content) -&gt; some View {
        ZStack {
            if isBlur {
                content
                    .background(linearGradient())
                    .blur(radius: 3)
            }

            content
                .background(linearGradient())
        }
    }
    
    @ViewBuilder
    private func linearGradient() -&gt; some View {
        LinearGradient(colors: [Colors.black, Colors.blue],
                       startPoint: .topLeading,
                       endPoint: .bottomTrailing)
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .strokeBorder(.white.opacity(0.5), lineWidth: 1)
            )
            .clipShape(RoundedRectangle(cornerRadius: cornerRadius))
    }
}

extension View {
    func linearGradientBlur(isBlur: Bool = false, 
                            cornerRadius: CGFloat = 15) -&gt; some View {
        self.modifier(BlurWithLinearGradient(isBlur: isBlur, 
                                             cornerRadius: cornerRadius))
    }
}" style="color:#f6f6f4;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">BlurWithLinearGradient</span><span style="color: #F6F6F4">: ViewModifier {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> isBlur: </span><span style="color: #97E1F1; font-style: italic">Bool</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> cornerRadius: CGFloat</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">body</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">content</span><span style="color: #F6F6F4">: Content) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        ZStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> isBlur {</span></span>
<span class="line"><span style="color: #F6F6F4">                content</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">background</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">linearGradient</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">blur</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">radius</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">3</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">            content</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">background</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">linearGradient</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@ViewBuilder</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">linearGradient</span><span style="color: #F6F6F4">() </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">LinearGradient</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">colors</span><span style="color: #F6F6F4">: [Colors.black, Colors.blue],</span></span>
<span class="line"><span style="color: #F6F6F4">                       </span><span style="color: #97E1F1">startPoint</span><span style="color: #F6F6F4">: .topLeading,</span></span>
<span class="line"><span style="color: #F6F6F4">                       </span><span style="color: #97E1F1">endPoint</span><span style="color: #F6F6F4">: .bottomTrailing)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">overlay</span><span style="color: #F6F6F4">(</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">RoundedRectangle</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: cornerRadius)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">strokeBorder</span><span style="color: #F6F6F4">(.white.</span><span style="color: #97E1F1">opacity</span><span style="color: #F6F6F4">(</span><span style="color: #BF9EEE">0.5</span><span style="color: #F6F6F4">), </span><span style="color: #97E1F1">lineWidth</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">1</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            )</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">clipShape</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">RoundedRectangle</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: cornerRadius))</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">extension</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">View</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">linearGradientBlur</span><span style="color: #F6F6F4">(</span><span style="color: #62E884; font-style: italic">isBlur</span><span style="color: #F6F6F4">: </span><span style="color: #97E1F1; font-style: italic">Bool</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">false</span><span style="color: #F6F6F4">, </span></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #62E884; font-style: italic">cornerRadius</span><span style="color: #F6F6F4">: CGFloat </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">15</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">modifier</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">BlurWithLinearGradient</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">isBlur</span><span style="color: #F6F6F4">: isBlur, </span></span>
<span class="line"><span style="color: #F6F6F4">                                             </span><span style="color: #97E1F1">cornerRadius</span><span style="color: #F6F6F4">: cornerRadius))</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="380" height="233" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-18-at-09.57.57.png" alt="" class="wp-image-2689" style="width:358px;height:auto" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-18-at-09.57.57.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/12/Screenshot-2023-12-18-at-09.57.57-300x184.png 300w" sizes="auto, (max-width: 380px) 100vw, 380px" /></figure>
</div>


<p><strong>References</strong>: <br><a href="https://developer.apple.com/documentation/swiftui/viewmodifier" target="_blank" rel="noreferrer noopener">https://developer.apple.com/documentation/swiftui/viewmodifier</a></p>
<p>The post <a href="https://blog.tomosia.com.vn/custom-view-modifiers-trong-swiftui/">Custom View Modifiers trong SwiftUI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/custom-view-modifiers-trong-swiftui/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Có gì HOT trong Swift 5.9 🔥🔥</title>
		<link>https://blog.tomosia.com.vn/co-gi-hot-trong-swift-5-9-%f0%9f%94%a5%f0%9f%94%a5/</link>
					<comments>https://blog.tomosia.com.vn/co-gi-hot-trong-swift-5-9-%f0%9f%94%a5%f0%9f%94%a5/#comments</comments>
		
		<dc:creator><![CDATA[Thanh Nguyen]]></dc:creator>
		<pubDate>Tue, 17 Oct 2023 02:40:21 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=1130</guid>

					<description><![CDATA[<p>01. Sử dụng if và switch như biểu thức Đến mãi Swift 5.9, thì từ khoá if và&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/co-gi-hot-trong-swift-5-9-%f0%9f%94%a5%f0%9f%94%a5/">Có gì HOT trong Swift 5.9 🔥🔥</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:100%">
<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading has-large-font-size" id="c3f6"><span id="01-su-dung-if-va-switch-nhu-bieu-thuc">01. Sử dụng <strong>if</strong> và <strong>switch</strong> như biểu thức</span></h2>



<p id="7b61">Đến mãi Swift 5.9, thì từ khoá <strong>if</strong> và <strong>switch</strong> chỉ được sử dụng như các câu lệnh!</p>



<p id="588a">Điều này có nghĩa là không thể gán trực tiếp kết quả của <strong>if</strong> hoặc <strong>switch</strong> vào một biến hoặc truyền nó như một tham số  </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.425323486328125px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="let bullet = 
  isRoot &amp;&amp; (count == 0 || !willExpand) ? &quot;&quot;
    : count == 0    ? &quot;- &quot;
    : maxDepth &lt;= 0 ? &quot;&gt;&quot; : &quot;&lt;&quot;" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> bullet </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span></span>
<span class="line"><span style="color: #F8F8F2">  isRoot </span><span style="color: #FF79C6">&amp;&amp;</span><span style="color: #F8F8F2"> (count </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">||</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">!</span><span style="color: #F8F8F2">willExpand) </span><span style="color: #FF79C6">?</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> count </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">?</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">- </span><span style="color: #E9F284">&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> maxDepth </span><span style="color: #FF79C6">&lt;=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">?</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">&gt;</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2"> </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">&lt;</span><span style="color: #E9F284">&quot;</span></span></code></pre></div>



<p id="6177">Hiện tại ở Swift 5.9 này, <strong>if</strong>&nbsp;và&nbsp;<strong>switch</strong>&nbsp;có thể được sử dụng như một biểu thức!</p>



<p id="536d">Các biểu thức bậc ba loằng ngoằng nhìn rối mắt bây giờ có thể thay thế bằng điều kiện <strong>if</strong> giúp chúng ta có thể dễ đọc hơn nhiều:  </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.42535400390625px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="let bullet = 
    if isRoot &amp;&amp; (count == 0 || !willExpand) { &quot;&quot; }
    else if count == 0 { &quot;- &quot; }
    else if maxDepth &lt;= 0 { &quot;&gt;&quot; }
    else { &quot;&lt;&quot; }" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> bullet </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> isRoot </span><span style="color: #FF79C6">&amp;&amp;</span><span style="color: #F8F8F2"> (count </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">||</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">!</span><span style="color: #F8F8F2">willExpand) { </span><span style="color: #E9F284">&quot;&quot;</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> count </span><span style="color: #FF79C6">==</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2"> { </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">- </span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> maxDepth </span><span style="color: #FF79C6">&lt;=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9">0</span><span style="color: #F8F8F2"> { </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">&gt;</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> { </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">&lt;</span><span style="color: #E9F284">&quot;</span><span style="color: #F8F8F2"> }</span></span></code></pre></div>



<p id="6a09">Không những thế, chúng ta có thể sử dụng chức năng mới này với <strong>if let</strong> hoặc <strong>guard let</strong> để unwrap biến optionals: </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.425346374511719px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="let attributedName =
        if let displayName, !displayName.isEmpty {
            AttributedString(markdown: displayName)
        } else {
            &quot;Untitled&quot;
        } " style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> attributedName </span><span style="color: #FF79C6">=</span></span>
<span class="line"><span style="color: #F8F8F2">        </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> displayName, </span><span style="color: #FF79C6">!</span><span style="color: #F8F8F2">displayName.</span><span style="color: #BD93F9">isEmpty</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #8BE9FD">AttributedString</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD">markdown</span><span style="color: #F8F8F2">: displayName)</span></span>
<span class="line"><span style="color: #F8F8F2">        } </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">            </span><span style="color: #E9F284">&quot;</span><span style="color: #F1FA8C">Untitled</span><span style="color: #E9F284">&quot;</span></span>
<span class="line"><span style="color: #F8F8F2">        } </span></span></code></pre></div>



<h2 class="wp-block-heading" id="6d86"><span id="02-parameter-packs">02. Parameter Packs</span></h2>



<p id="770c">Nếu bạn đã từng phải viết những đoạn code cần xử lý nhiều loại generic không xác định, và Swift không trợ cho việc đa dạng generics  </p>



<p id="6b43">Và cách duy nhất bạn có thể làm là triển khai tất cả các generics vào một function:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:16.854156494140625px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="struct Request&lt;Result&gt; { ... }

struct RequestEvalutor {
  func evalute&lt;Result&gt;(_:) -&gt; (Result)
  
  func evalute&lt;R1, R2&gt;(_:,_:) -&gt; (R1, R2)
  
  func evalute&lt;R1, R2, R3&gt;(_:,_:,_:) -&gt; (R1, R2, R3)
  
  func evalute&lt;R1, R2, R3, R4&gt;(_:,_:,_:,_:) -&gt; (R1, R2, R3, R4)
  
  func evalute&lt;R1, R2, R3, R4, R5&gt;(_:,_:,_:,_:,_:) -&gt; (R1, R2, R3, R4, R5)
}

let request = RequestEvalutor()
let result = request.evaluate(r1, r2, r3, r4, r5)" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">struct</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Request</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">Result</span><span style="color: #F8F8F2">&gt; { </span><span style="color: #FF79C6">...</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">struct</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">RequestEvalutor</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">evalute</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">Result</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (Result)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">evalute</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">R1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R2</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (R1, R2)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">evalute</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">R1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R2</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R3</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (R1, R2, R3)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">evalute</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">R1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R2</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R3</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R4</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (R1, R2, R3, R4)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">evalute</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">R1</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R2</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R3</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R4</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">R5</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:,</span><span style="color: #50FA7B; font-style: italic">_</span><span style="color: #F8F8F2">:) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (R1, R2, R3, R4, R5)</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> request </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">RequestEvalutor</span><span style="color: #F8F8F2">()</span></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> result </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> request.</span><span style="color: #8BE9FD">evaluate</span><span style="color: #F8F8F2">(r1, r2, r3, r4, r5)</span></span></code></pre></div>



<p id="fd05">Mặc dù cách làm như trên có vẻ như không được tốt lắm nhưng các framework như SwiftUI và Combine cũng dựa vào cách làm như trên</p>



<p id="5c06">Nhưng không may một điều là cách làm như trên sẽ gặp một nhược điểm lớn vì bạn không thể triển khai một số lượng đối số chồng chất vô hạn, vì vậy luôn có một giới hạn về số lượng đối số mà hàm của bạn có thể xử lý: </p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.42535400390625px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="let result = request.evaluate(r1, r2, r3, r4, r5, r6, r7)
// This code will be error: Extra argrument in call" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> result </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> request.</span><span style="color: #8BE9FD">evaluate</span><span style="color: #F8F8F2">(r1, r2, r3, r4, r5, r6, r7)</span></span>
<span class="line"><span style="color: #6272A4">// This code will be error: Extra argrument in call</span></span></code></pre></div>



<p id="7dde">Một ví dụ thực tế là khi bạn cố gắng implement thật nhiều child view vào 1 VStack hoặc HStack thì SwiftUI chỉ có thể thêm được tối đa 10 child view.</p>



<p id="7cb2">Nhưng nếu bạn sử dụng Xcode 15 và thử thêm 11 child view thì một điều tuyệt vời là không còn warning giới hạn nào ở đây nữa. Có thể thêm bao nhiêu tuỳ thích !</p>



<p id="b654">Vì trong Swift 5.9 này có một tính năng mới gọi là Parameter Packs:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="720" height="181" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/1_ZF7w8J2uZDPb-Zl1LsgaNQ-1.webp" alt="" class="wp-image-1141" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1_ZF7w8J2uZDPb-Zl1LsgaNQ-1.webp 720w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1_ZF7w8J2uZDPb-Zl1LsgaNQ-1-300x75.webp 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1_ZF7w8J2uZDPb-Zl1LsgaNQ-1-380x96.webp 380w" sizes="auto, (max-width: 720px) 100vw, 720px" /></figure>



<p id="ee89">Sử dụng từ khoá&nbsp;<strong>each</strong>, để khai báo một Parameter Pack.</p>



<p id="64f4">Và sau đó, thông qua từ khoá <strong>repeat</strong>, chúng ta có thể sử dụng Parameter Pack này, chẳng hạn để khai báo các đối số của hàm hoặc xác định một tuple type.</p>



<p>Swift 5.9:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:16.854167938232422px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="func zip&lt;each T&gt;(_ input: repeat each T) -&gt; (A, B, C, D) {
    return (repeat each input)
}

//Example
func pairUp&lt;each T, each U&gt;(firstPeople: repeat each T, 
                            secondPeople: repeat each U)
                           -&gt; (repeat (first: each T, second: each U)) {
    return (repeat (each firstPeople, each secondPeople))
}

let result = pairUp(firstPeople: johnny, derek, 
                     secondPeople: kate, kevin)
" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">zip</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">each</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">T</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B">_</span><span style="color: #F8F8F2"> </span><span style="color: #FFB86C; font-style: italic">input</span><span style="color: #F8F8F2">: repeat each T) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (A, B, C, D) {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> (</span><span style="color: #FF79C6">repeat</span><span style="color: #F8F8F2"> each input)</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6272A4">//Example</span></span>
<span class="line"><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">pairUp</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">each</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">T</span><span style="color: #F8F8F2">, </span><span style="color: #BD93F9; font-style: italic">each</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">U</span><span style="color: #F8F8F2">&gt;(</span><span style="color: #50FA7B; font-style: italic">firstPeople</span><span style="color: #F8F8F2">: repeat each T, </span></span>
<span class="line"><span style="color: #F8F8F2">                            </span><span style="color: #50FA7B; font-style: italic">secondPeople</span><span style="color: #F8F8F2">: repeat each U)</span></span>
<span class="line"><span style="color: #F8F8F2">                           </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (</span><span style="color: #FF79C6">repeat</span><span style="color: #F8F8F2"> (</span><span style="color: #8BE9FD">first</span><span style="color: #F8F8F2">: each T, </span><span style="color: #8BE9FD">second</span><span style="color: #F8F8F2">: each U)) {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> (</span><span style="color: #FF79C6">repeat</span><span style="color: #F8F8F2"> (each firstPeople, each secondPeople))</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> result </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">pairUp</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD">firstPeople</span><span style="color: #F8F8F2">: johnny, derek, </span></span>
<span class="line"><span style="color: #F8F8F2">                     </span><span style="color: #8BE9FD">secondPeople</span><span style="color: #F8F8F2">: kate, kevin)</span></span>
<span class="line"></span></code></pre></div>



<p>Trước đây:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.425348281860352px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="func zip&lt;T₁, T₂,‥․, Tₙ&gt;(_ i₁: T₁, _ i₂:T₂,‥․,_ iₙ: Tₙ) -&gt; (T₁, T₂,‥․,Tₙ) {
    return (i₁, i₂,‥․, iₙ)
}" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">func</span><span style="color: #F8F8F2"> </span><span style="color: #50FA7B">zip</span><span style="color: #F8F8F2">&lt;</span><span style="color: #BD93F9; font-style: italic">T</span><span style="color: #F8F8F2">₁, T₂,‥․, Tₙ&gt;(</span><span style="color: #50FA7B">_</span><span style="color: #F8F8F2"> </span><span style="color: #FFB86C; font-style: italic">i₁</span><span style="color: #F8F8F2">: T₁, </span><span style="color: #50FA7B">_</span><span style="color: #F8F8F2"> </span><span style="color: #FFB86C; font-style: italic">i₂</span><span style="color: #F8F8F2">:T₂,‥․,</span><span style="color: #50FA7B">_</span><span style="color: #F8F8F2"> </span><span style="color: #FFB86C; font-style: italic">iₙ</span><span style="color: #F8F8F2">: Tₙ) </span><span style="color: #FF79C6">-&gt;</span><span style="color: #F8F8F2"> (T₁, T₂,‥․,Tₙ) {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">return</span><span style="color: #F8F8F2"> (i₁, i₂,</span><span style="color: #FF79C6">‥․</span><span style="color: #F8F8F2">, iₙ)</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span></code></pre></div>



<h2 class="wp-block-heading" id="a5b5"><span id="03-macros">03. Macros</span></h2>



<p>Ở Swift 5.9, ngôn ngữ swift đã cho ra mắt tính năng <strong>Macros</strong>, không kém gì các plugin có khả năng generating code </p>



<p id="0771">Dưới đây là đoạn code điển hình mà macro có thể tạo cho bạn: xác định một thuộc tính sau khi tính toán cho từng case trong một enum</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:16.854169845581055px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="enum Path {
  case relative(String)
  case absolute(String)
}

let absPaths = paths.filter { $0.isAbsolute }

extension Path {
  var isAbsolute:Bool {
    if case .absolute = self { true }
    else { false }
  }
}

extension Path {
  var isRelative:Bool {
    if case .relative = self { true }
    else { false }
  }
}" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">enum</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Path</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">relative</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">absolute</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> absPaths </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> paths.</span><span style="color: #8BE9FD">filter</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9; font-style: italic">$0</span><span style="color: #F8F8F2">.isAbsolute }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">extension</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Path</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">var</span><span style="color: #F8F8F2"> isAbsolute:</span><span style="color: #8BE9FD; font-style: italic">Bool</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> .absolute </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">self</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">true</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">false</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">extension</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Path</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">var</span><span style="color: #F8F8F2"> isRelative:</span><span style="color: #8BE9FD; font-style: italic">Bool</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> .relative </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">self</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">true</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">    </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">false</span><span style="color: #F8F8F2"> }</span></span>
<span class="line"><span style="color: #F8F8F2">  }</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span></code></pre></div>



<p id="2cc8">Ở Swift 5.9 bạn có thể xoá đoạn code trên và thay vào đó, bạn chỉ cần thêm&nbsp;<strong>@CaseDetection</strong> enum của bạn :</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:8.425323486328125px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="@CaseDetction
enum Path {
  case relative(String)
  case absolute(String)
}

let absPaths = paths.filter { $0.isAbsolute }" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">@CaseDetction</span></span>
<span class="line"><span style="color: #FF79C6">enum</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Path</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">relative</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">absolute</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> absPaths </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> paths.</span><span style="color: #8BE9FD">filter</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9; font-style: italic">$0</span><span style="color: #F8F8F2">.isAbsolute }</span></span></code></pre></div>



<p id="8fea">Tại thời điểm biên dịch, macro này sau đó sẽ được trình biên dịch Swift generate đoạn code trên giúp bạn:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" 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;--cbp-line-number-color:#F8F8F2;--cbp-line-number-width:16.854156494140625px;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#333545;color:#efefe1">Swift</span><span role="button" tabindex="0" data-code="@CaseDetction
enum Path {
  case relative(String)
  case absolute(String)
                               @CaseDection will generate this code 
** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
*                                                                  *
* var isAbsolute:Bool {                                            *
*  if case .absolute = self { true }                               *
*  else { false }                                                  *
* }                                                                *
*                                                                  *
* var isRelative:Bool {                                            *
*  if case .relative = self { true }                               *
*  else { false }                                                  *
* }                                                                *
*                                                                  *
** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

}

let absPaths = paths.filter { $0.isAbsolute }" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #FF79C6">@CaseDetction</span></span>
<span class="line"><span style="color: #FF79C6">enum</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD; font-style: italic">Path</span><span style="color: #F8F8F2"> {</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">relative</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> </span><span style="color: #8BE9FD">absolute</span><span style="color: #F8F8F2">(</span><span style="color: #8BE9FD; font-style: italic">String</span><span style="color: #F8F8F2">)</span></span>
<span class="line"><span style="color: #F8F8F2">                               </span><span style="color: #FF79C6">@CaseDection</span><span style="color: #F8F8F2"> will generate this code </span></span>
<span class="line"><span style="color: #FF79C6">**</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">                                                                  </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">var</span><span style="color: #F8F8F2"> isAbsolute:</span><span style="color: #8BE9FD; font-style: italic">Bool</span><span style="color: #F8F8F2"> {                                            </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> .absolute </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">self</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">true</span><span style="color: #F8F8F2"> }                               </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">false</span><span style="color: #F8F8F2"> }                                                  </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> }                                                                </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">                                                                  </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">var</span><span style="color: #F8F8F2"> isRelative:</span><span style="color: #8BE9FD; font-style: italic">Bool</span><span style="color: #F8F8F2"> {                                            </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">if</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">case</span><span style="color: #F8F8F2"> .relative </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> </span><span style="color: #BD93F9; font-style: italic">self</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">true</span><span style="color: #F8F8F2"> }                               </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">else</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9">false</span><span style="color: #F8F8F2"> }                                                  </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> }                                                                </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">*</span><span style="color: #F8F8F2">                                                                  </span><span style="color: #FF79C6">*</span></span>
<span class="line"><span style="color: #FF79C6">**</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span><span style="color: #FF79C6">*</span><span style="color: #F8F8F2"> </span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #FF79C6">let</span><span style="color: #F8F8F2"> absPaths </span><span style="color: #FF79C6">=</span><span style="color: #F8F8F2"> paths.</span><span style="color: #8BE9FD">filter</span><span style="color: #F8F8F2"> { </span><span style="color: #BD93F9; font-style: italic">$0</span><span style="color: #F8F8F2">.isAbsolute }</span></span></code></pre></div>



<p id="514a">Và đó là 3 tính năng mới của Swift 5.9 mà mình muốn giới thiệu cho các bạn !!!</p>



<p id="2967"><em>Hi vong các bạn sẽ apply các tính năng này vào dự án của mình  </em>🙌</p>
</div></div>
</div>
</div>
<p>The post <a href="https://blog.tomosia.com.vn/co-gi-hot-trong-swift-5-9-%f0%9f%94%a5%f0%9f%94%a5/">Có gì HOT trong Swift 5.9 🔥🔥</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/co-gi-hot-trong-swift-5-9-%f0%9f%94%a5%f0%9f%94%a5/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>SwiftUI vs Flutter : Cách xây dựng UI</title>
		<link>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/</link>
					<comments>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/#comments</comments>
		
		<dc:creator><![CDATA[Le Quoc]]></dc:creator>
		<pubDate>Sun, 08 Oct 2023 08:30:34 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[Flutter]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=934</guid>

					<description><![CDATA[<p>Giới thiệu Vào đầu năm 2019, Flutter phiên bản 1.0 đã được phát hành chính thức. Còn SwiftUI&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/">SwiftUI vs Flutter : Cách xây dựng UI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="427" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1024x427.png" alt="" class="wp-image-935" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1024x427.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-300x125.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-768x320.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-380x158.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-800x333.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0-1160x483.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h0.png 1200w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="gioi-thieu" class="wp-block-heading">Giới thiệu</h2>



<p>Vào đầu năm 2019, Flutter phiên bản 1.0 đã được phát hành chính thức. Còn SwiftUI cũng được giới thiệu bởi Apple vào năm 2019, Apple nói rằng SwiftUI là tương lai của việc phát triển ứng dụng trên các nền tảng của Apple (vì nó là đa nền tảng, cho phép bạn phát triển ứng dụng iOS, MacOS, WatchOS và tvOS chỉ bằng một mã nguồn duy nhất).</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Thay vì nói lí thuyết, ta sẽ thử viết 1 màn hình để play video. Xuyên suốt quá trình dựng UI, chúng ta sẽ cùng code và phân tích ưu điểm của mỗi framework nhé.</p>
</blockquote>



<h2 id="1-ui-tong-quan" class="wp-block-heading">1/ UI tổng quan</h2>



<p><strong>Flutter</strong> trước nhé.</p>



<p><strong>TopView</strong> class sẽ hiển thị tabs, ta sẽ tạo class <strong>HomeView</strong>. Ở <strong>HomeView</strong>, ta dùng widget <strong>CupertinoPageScaffold</strong>. Sẽ có <strong>navi</strong>, và 1 <strong>SingleChildScrollView</strong> để đảm bảo tính đầy đủ dữ liệu ở các thiết bị màn hình nhỏ.</p>



<p><strong>TopView</strong> và <strong>HomeView</strong> kế thừa từ StatefulWidget.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="875" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1024x875.png" alt="" class="wp-image-936" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1024x875.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-300x256.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-768x656.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1536x1312.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-380x325.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-800x683.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1-1160x991.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/1.png 1702w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="845" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1024x845.png" alt="" class="wp-image-938" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1024x845.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-300x248.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-768x634.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1536x1268.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-380x314.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-800x660.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1-1160x958.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/2-1.png 1696w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Rồi, đến <strong>SwiftUI</strong>, cũng sẽ hiển thị tabs tương tự</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="692" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1024x692.png" alt="" class="wp-image-941" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1024x692.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-300x203.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-768x519.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-380x257.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-800x541.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1-1160x784.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/Screen-Shot-2023-10-08-at-15.08.24-1.png 1438w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>HomeScreen</strong> struct sẽ hiển thị video player và danh sách video.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="499" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1024x499.png" alt="" class="wp-image-939" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1024x499.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-300x146.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-768x375.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1536x749.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-2048x999.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-380x185.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-800x390.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3-1160x566.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/3.png 2116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="2-ui-ve-video-player" class="wp-block-heading"><strong>2/ UI về video player</strong></h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="632" height="1024" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-632x1024.webp" alt="" class="wp-image-942" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-632x1024.webp 632w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-185x300.webp 185w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4-380x615.webp 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h4.webp 704w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<p>Về phía <strong>Flutter</strong>, mỗi UI element được đặt trong 1 vị trí thích hợp bằng cách set <strong>MainAxisAlignment</strong> của <strong>Column</strong> widget và <strong>Row</strong> widget. <strong>CupertinoSlider</strong> widget sẽ đảm nhận việc hiển thị thanh slider. </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1014" height="1024" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1014x1024.png" alt="" class="wp-image-943" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1014x1024.png 1014w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-297x300.png 297w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-150x150.png 150w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-768x776.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-80x80.png 80w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-380x384.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-800x808.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6-1160x1171.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/6.png 1428w" sizes="auto, (max-width: 1014px) 100vw, 1014px" /></figure>



<p>Đối với <strong>SwiftUI</strong>, <strong>BigMovieCellOverlay</strong> struct sẽ đảm nhận việc hiển thị player thông qua <strong>VStack</strong> và <strong>HStack</strong>. <strong>Rectangle</strong> được bọc trong <strong>ZStack</strong> sẽ đóng vai trò <strong>Slider</strong> progress của video. </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="822" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1024x822.png" alt="" class="wp-image-944" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1024x822.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-300x241.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-768x617.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1536x1234.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-380x305.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-800x643.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7-1160x932.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/7.png 1880w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="3-ui-ve-list" class="wp-block-heading">3/ UI về List</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="539" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1024x539.png" alt="" class="wp-image-945" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1024x539.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-300x158.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-768x404.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-380x200.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-800x421.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8-1160x610.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/8.png 1266w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="a-scroll-ngang" class="wp-block-heading">a/ Scroll ngang</h2>



<p>Về phía <strong>Flutter</strong>, Widget <strong>ListView</strong> trong <strong>Broadcasting</strong> được set <strong>Axis.horizontal</strong>, được xếp chồng bằng cách sử dụng <strong>Stack</strong>. Mỗi thumbnail được triển khai trong lớp <strong>BigPicCell</strong> được mô tả bên dưới.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="481" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1024x481.png" alt="" class="wp-image-946" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1024x481.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-300x141.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-768x361.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-380x179.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-800x376.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9-1160x545.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/9.png 1226w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Về phía <strong>SwiftUI</strong>, mỗi thumbnail được triển khai ở BigPicCell struct.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="528" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1024x528.png" alt="" class="wp-image-947" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1024x528.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-300x155.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-768x396.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1536x792.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-2048x1056.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-380x196.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-800x413.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10-1160x598.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h10.png 2110w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="b-decoration" class="wp-block-heading">b/ Decoration</h2>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="542" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1024x542.png" alt="" class="wp-image-949" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1024x542.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-300x159.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-768x406.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-380x201.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-800x423.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11-1160x614.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/hj11.png 1278w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Flutter</strong>, <strong>League</strong> class gần giống với <strong>Broadcasting</strong> class. Mỗi thumbnail được triển khai ở class bên dưới.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="723" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-1024x723.png" alt="" class="wp-image-950" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-1024x723.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-300x212.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-768x542.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-200x140.png 200w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-380x268.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12-800x565.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h12.png 1116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>BigPicCell</strong> class sẽ đảm nhận việc hiển thị thumbnails và thông tin, được bọc bởi 1 <strong>Column</strong> widget</p>



<p>Thumbnail dùng <strong>BoxDecoration</strong> widget tạo cái viền vàng xung quanh.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="437" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1024x437.png" alt="" class="wp-image-951" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1024x437.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-300x128.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-768x328.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1536x656.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-380x162.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-800x342.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1-1160x495.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h13-1.png 1836w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Tương tự thì <strong>SwiftUI</strong> cũng có <strong>League</strong> và <strong>BigPicCell</strong><em> </em>struct sẽ đảm nhận việc hiển thị thumbnails và thông tin.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="703" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1024x703.png" alt="" class="wp-image-954" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1024x703.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-300x206.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-768x527.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1536x1054.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-2048x1405.png 2048w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-380x261.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-800x549.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1-1160x796.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h14-1.png 2116w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>ZStack</strong> set <code>border(Color.yellow, width: isWatchingId == id ? 2 : 0)</code> để hiển thị viền vàng xung quanh. Khi tap vào, video sẽ được play và <strong>Text(“ Live”)</strong> sẽ được hiển thị ở thumbnail. </p>



<p><code>Group {isRecord ? nil : LiveLabel()}<br>.padding(EdgeInsets.init(top: 15.0, leading: 15.0, bottom: 0.0, trailing: 0.0)).</code></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="770" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1024x770.png" alt="" class="wp-image-952" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1024x770.png 1024w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-300x225.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-768x578.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1536x1155.png 1536w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-380x286.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-800x602.png 800w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15-1160x872.png 1160w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/h15.png 1944w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 id="4-summary" class="wp-block-heading">4/Summary:</h2>



<p>Đã trải qua quãng thời gian làm việc khá lâu với SwiftUI, trước đó là UIKit và Interfacebuilder của iOS. Bằng so sánh chủ quan của mình, tôi đánh giá cao về sự hiện đại trong cách Flutter framework hỗ trợ chúng ta dựng UI.</p>



<p>Còn về phía bạn, hãy chia sẽ cảm nghĩ của mình ở phần bình luận nhé.</p>
<p>The post <a href="https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/">SwiftUI vs Flutter : Cách xây dựng UI</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/swiftui-vs-flutter-cach-xay-dung-ui/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Automation Test for iOS (part 1)</title>
		<link>https://blog.tomosia.com.vn/automation-test-for-ios-part-1/</link>
					<comments>https://blog.tomosia.com.vn/automation-test-for-ios-part-1/#comments</comments>
		
		<dc:creator><![CDATA[Vo Phuong]]></dc:creator>
		<pubDate>Tue, 03 Oct 2023 07:16:09 +0000</pubDate>
				<category><![CDATA[IOS]]></category>
		<category><![CDATA[Automation Test]]></category>
		<category><![CDATA[KIF]]></category>
		<guid isPermaLink="false">https://blog.tomosia.com.vn/?p=299</guid>

					<description><![CDATA[<p>Đối với công việc lâp trình phần mềm thì Unit Test và UI Test là những việc vô&#8230;</p>
<p>The post <a href="https://blog.tomosia.com.vn/automation-test-for-ios-part-1/">Automation Test for iOS (part 1)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Đối với công việc lâp trình phần mềm thì Unit Test và UI Test là những việc vô cùng quan trọng trong việc bảo đảm 1 phần mềm đạt chất lượng tốt. Bài viết này, mình chỉ tập trung chủ yếu vào UI Test. Với lập trình viên iOS thì việc viết UI Test đã có sự support từ Apple với XCTest framework. Nhưng sau khi làm thử với XCTest framework thì thấy nó khó cho việc viết UITest cho chúng ta. Nên mình đã tìm hiểu được 1 framework cho việc viết UITest là <a href="https://github.com/kif-framework/KIF"><strong>KIF framewok</strong></a></p>



<h2 id="1-cai-dat-kif" class="wp-block-heading">1. Cài đặt KIF</h2>



<p>Điều đầu tiên, để sử dụng được KIF thì chúng ta cần tạo target cho việc test của chúng ta. Ở đây, KIF không sử dụng&nbsp;<strong>iOS UI Testing Bundle</strong>&nbsp;mà sử dụng&nbsp;<strong>iOS Unit Testing Bundle</strong>.Nên trong trường hợp bạn nào đã có unit test ở trong project thì cần chọn 1 trong 2 target để chạy test.</p>



<p>Chọn dự án của bạn trong Xcode và nhấp vào &#8220;Add Target&#8221; ở góc dưới bên trái của trình soạn thảo. Chọn iOS -&gt; Test -&gt; iOS Unit Testing Bundle. Hãy cung cấp cho nó một tên như &#8220;Acceptance Tests&#8221;, &#8220;UI Tests&#8221;, hoặc cái gì đó cho thấy ý định của quá trình test của bạn.</p>



<p>Acceptance Test Target của bạn được tạo và sẽ có thêm 1 file đi cùng với nó, ex: &#8220;Acceptance_Tests.swift&#8221; khớp với tên target. Hãy xoá nó đi.</p>



<h3 id="1-1-cai-dat-voi-pod" class="wp-block-heading">1.1 Cài đặt với Pod</h3>



<p>Khi Test Target của bạn đã được thiết lập, thêm đoạn mã dưới đây vào Podfile của bạn. Sử dụng đúng target cần cài đặt KIF</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:#282A36"><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="target 'Your Apps' do
  ...
end

target 'Acceptance Tests' do
  pod 'KIF', :configurations =&gt; ['Debug']
end" style="color:#F8F8F2;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="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F8F8F2">target &#39;Your Apps&#39; </span><span style="color: #FF79C6">do</span></span>
<span class="line"><span style="color: #F8F8F2">  </span><span style="color: #FF79C6">...</span></span>
<span class="line"><span style="color: #F8F8F2">end</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F8F8F2">target &#39;Acceptance Tests&#39; </span><span style="color: #FF79C6">do</span></span>
<span class="line"><span style="color: #F8F8F2">  pod &#39;KIF&#39;, </span><span style="color: #FF79C6">:</span><span style="color: #F8F8F2">configurations </span><span style="color: #FF79C6">=&gt;</span><span style="color: #F8F8F2"> [&#39;Debug&#39;]</span></span>
<span class="line"><span style="color: #F8F8F2">end</span></span></code></pre></div>



<p>Sau khi chạy <code>pod install</code> thì công việc tiếp theo là làm theo <strong>Cấu hình Test Target</strong> ở bên dưới để cấu hình thích hợp cho nó</p>



<h3 id="1-2-cai-voi-static-library" class="wp-block-heading">1.2 Cài với static library</h3>



<p>Để cài đặt KIF, bạn cần liên kết static library libKIF trực tiếp vào ứng dụng của bạn. Tải xuống nguồn từ&nbsp;<a href="https://github.com/kif-framework/KIF">KIF</a>&nbsp;và làm theo hướng dẫn dưới đây.</p>



<p>Chúng tôi sẽ sử dụng một dự án đơn giản làm ví dụ và bạn có thể tìm thấy nó trong Documentation/Examples/Testable Swift trong reposity của KIF.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="825" height="453" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1.png" alt="" class="wp-image-300" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1.png 825w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1-300x165.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1-768x422.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1-380x209.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-1-800x439.png 800w" sizes="auto, (max-width: 825px) 100vw, 825px" /></figure>



<h4 id="them-kif-vao-du-an" class="wp-block-heading">Thêm KIF vào dự án</h4>



<p>Bước đầu tiên là thêm dự án KIF vào thư mục con của ./Frameworks/KIF của dự án. Nếu dự án của bạn sử dụng Git để kiểm soát phiên bản, bạn có thể sử dụng submodules để cập nhật trong tương lai dễ dàng hơn:</p>



<pre class="wp-block-preformatted">cd /path/to/MyApplicationSource
mkdir Frameworks
git submodule add https://github.com/kif-framework/KIF.git Frameworks/KIF</pre>



<h4 id="them-kif-vao-workspace" class="wp-block-heading">Thêm KIF vào Workspace</h4>



<p>Hãy để dự án của bạn biết về KIF bằng cách thêm dự án KIF vào không gian làm việc cùng với dự án chính của bạn. Tìm KIF.xcodeproj trong Finder và kéo nó vào Project Navigator (⌘1).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="825" height="453" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2.png" alt="" class="wp-image-301" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2.png 825w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2-300x165.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2-768x422.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2-380x209.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-2-800x439.png 800w" sizes="auto, (max-width: 825px) 100vw, 825px" /></figure>



<h3 id="1-3-cau-hinh-test-target" class="wp-block-heading">1.3 Cấu hình Test Target</h3>



<p>Bây giờ, bạn đã có test target cho việc viết UI Test của bạn. Tiếp theo là thêm các thư viện cần thiết cho dự án, chúng ta chọn trong <strong>Project Navigator</strong> và vào Test Target mà bạn đã tạo ra, chọn tab <strong>&#8220;Build Phases&#8221;</strong>. Trong phần <strong>&#8220;Link Binary With Libraries&#8221;</strong>, hãy nhấn nút &#8220;+&#8221;. Trong bảng xuất hiện, chọn &#8220;libKIF.a&#8221; và nhấp vào <strong>&#8220;Add&#8221;</strong>. Lặp lại quá trình cho <strong>CoreGraphics.framework</strong> và <strong>QuartzCore.framework</strong> . KIF yêu cầu IOKit.framework, nhưng nó không nằm trong các khuôn khổ hệ thống. Để liên kết đến nó, thêm &#8220;-framework IOKit&#8221; vào <strong>&#8220;Other Linker Flags&#8221;</strong>.</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3.png" alt="" class="wp-image-302" style="width:679px;height:373px" width="679" height="373" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3.png 825w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3-300x165.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3-768x422.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3-380x209.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-3-800x439.png 800w" sizes="auto, (max-width: 679px) 100vw, 679px" /></figure>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="825" height="453" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4.png" alt="" class="wp-image-303" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4.png 825w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4-300x165.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4-768x422.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4-380x209.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-4-800x439.png 800w" sizes="auto, (max-width: 825px) 100vw, 825px" /></figure>



<p>KIF được viết bằng Objective C nên các bạn cần thêm <code>-ObjC</code> vào <strong>&#8220;Other Linker Flags&#8221;</strong> trong Build Settings của Test Target</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="825" height="453" src="http://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5.png" alt="" class="wp-image-304" srcset="https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5.png 825w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5-300x165.png 300w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5-768x422.png 768w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5-380x209.png 380w, https://blog.tomosia.com.vn/wp-content/uploads/2023/10/image-5-800x439.png 800w" sizes="auto, (max-width: 825px) 100vw, 825px" /></figure>



<p>Ở phần này mình chỉ giới thiệu về việc thêm KIF framework vào dự án của chúng. Ở phần sau mình sẽ demo về KIF framework</p>



<p></p>



<p></p>
<p>The post <a href="https://blog.tomosia.com.vn/automation-test-for-ios-part-1/">Automation Test for iOS (part 1)</a> appeared first on <a href="https://blog.tomosia.com.vn">Tomoshare</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.tomosia.com.vn/automation-test-for-ios-part-1/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>
