Introduction
Kể từ iOS 10, Apple đã cung cấp rich notification
hỗ trợ push notification với các framework mới: UserNotifications
và UserNotificationsUI
.
Khi sử dụng các framework này, chúng ta có thể customize thông báo push notification với các khả năng:
- Customize type và
content/UI
của push notification. - Cung cấp các custom action và response tương ứng cho mỗi loại notification.
- Thay đổi content của các push notification trước khi hiển thị cho user.
- Customize các custom trigger của push notification theo các khoảng thời gian cụ thể hay theo các khu vực địa lý.
Bên cạnh việc support rich notification, Apple cũng đã hỗ trợ thêm interactive custom UI push notification trong iOS 13. Trước đây, chúng ta chỉ có thể customize các action mà user chỉ có thể tương tác giống như dạng action-sheet trên iPhone UI. Với kiểu interactive notification mới, chúng ta còn có thể hiển thị rất nhiều kiểu UI và tương tác trên giao diện push notification.
Đây thực sự là một thay đổi lớn cho phần push notification. Ví dụ, chúng ta có thể cung cấp các text fieldm switch, slider, stepper hoặc bất kỳ custom control nào mà ta muốn, có thể tùy ý customize push notification preview một cách dễ dàng.
Trong bài viết này, chúng ta sẽ xây dựng một interactive custom push notification UI để hiển thị một video preview Youtube với các button mà user có thể tương tác. Ngoài ra, user cũng có thể rate video và comment trên một text field. Cụ thể các task cần làm như sau:
- Setup project và các dependencies.
- Require push notification permission, type và category.
- Giả lập test push notification.
- Setup Notification Extension target infoplist.
- Setup UI cho notification content.
- Handle notification nhận được và các tương tác của user.
Để có thể giả lập test push notification trên simulator, chúng ta cần download và cài đặt tối thiểu Xcode 11.4 trở lên từ Apple developer website
Setup project and dependencies
Để bắt đầu, hãy tạo mới một Xcode project với một unique bundle identifier. Sau đó, chuyển đến tab Signing & Capabilities của project. Click button + Capabilities
rồi chọn push notification. Mục đích là enable tính năng push notification vào App ID của project.
Tiếp theo, chúng ta tạo một app extension target mới cho custom content notification UI. Từ menu bar, click File > New > Target
. Gõ notification trong text field filter rồi chọn Notification Content Extension. Đặt tên cho nó và click finish.
Sau đó, activate scheme vừa tạo trong alert dialog, rồi đóng project.
Tiếp tục, chúng ta sẽ init Cocoapods
cho project và khai báo các dependencies cần sử dụng. Mở terminal, navigate đến folder project hiện tại và gõ lệnh pod init
. Sau đó, mở Pod file
vừa tạo bằng text editor và thêm các dependencies sau:
- XCDYoutubeKit: Một library cho phép play video Youtube sử dụng
AVPlayer
- Cosmos: Một library custom control hiển thị rating dạng star.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'iRPC' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for iRPC pod "XCDYouTubeKit", "~> 2.9" pod 'Cosmos', '~> 21.0' end target 'iRPCNotificationExtension' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for iRPCNotificationExtension pod "XCDYouTubeKit", "~> 2.9" pod 'Cosmos', '~> 21.0' end |
Trong terminal, chạy lệnh pod install
để install các dependencies trên vào các target. Sau khi hoàn tất, mở file .xcworkspace
với Xcode và build thử Command + B
để đảm bảo mọi thứ hoạt động bình thường.
Register push notification
Tiếp theo, chúng ta cần phải register permission để cho phép push notification trong app sử dụng class UNUserNotificationCenter
. Import framework UserNotifications
trong AppDelegate
và thêm code trong method (_:didFinishLaunchingWithOptions:)
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">import</span> <span class="token builtin">UIKit</span> <span class="token keyword">import</span> <span class="token builtin">UserNotifications</span> <span class="token atrule">@UIApplicationMain</span> <span class="token keyword">class</span> <span class="token class-name">AppDelegate</span><span class="token punctuation">:</span> <span class="token builtin">UIResponder</span><span class="token punctuation">,</span> <span class="token builtin">UIApplicationDelegate</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">application</span><span class="token punctuation">(</span><span class="token number">_</span> application<span class="token punctuation">:</span> <span class="token builtin">UIApplication</span><span class="token punctuation">,</span> didFinishLaunchingWithOptions launchOptions<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token builtin">UIApplication</span><span class="token punctuation">.</span><span class="token builtin">LaunchOptionsKey</span><span class="token punctuation">:</span> <span class="token builtin">Any</span><span class="token punctuation">]</span><span class="token operator">?</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> center <span class="token operator">=</span> <span class="token builtin">UNUserNotificationCenter</span><span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span> center<span class="token punctuation">.</span><span class="token function">requestAuthorization</span><span class="token punctuation">(</span>options<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">.</span>alert<span class="token punctuation">,</span> <span class="token punctuation">.</span>sound<span class="token punctuation">,</span> <span class="token punctuation">.</span>badge<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token number">_</span><span class="token punctuation">,</span> <span class="token number">_</span> <span class="token keyword">in</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> notificationCategory <span class="token operator">=</span> <span class="token function">UNNotificationCategory</span><span class="token punctuation">(</span>identifier<span class="token punctuation">:</span> <span class="token string">"iRPCNotificationCategory"</span><span class="token punctuation">,</span> actions<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> intentIdentifiers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token builtin">UNUserNotificationCenter</span><span class="token punctuation">.</span><span class="token function">current</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setNotificationCategories</span><span class="token punctuation">(</span><span class="token punctuation">[</span>notificationCategory<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Đầu tiên, gọi method
requestAuthorization
và truyền vào mảng các option. Trong trường hợp này chúng ta cầnalert
,badge
vàsound
. - Thứ hai, init
UNNotificationCategory
và truyền vào một unique category identifier string. Ở đây, do chúng ta không cần các custom action nên truyền vào mảng action rỗng. - Cuối cùng, gọi method
setNotificationCategories
và truyền vào custom notification category vừa tạo vào.
Simulate remote push notification for testing
Kể từ Xcode 11.4 thì Apple cuối cùng cũng đã cung cấp tính năng mới cho phép giả lập push notification ở local. Cách làm rất đơn giản. Chúng ta chỉ cần tạo một file apns json
chứa content payload. Ngoài ra chúng ta cũng cần phải thêm một số key khác bên cạnh key aps
. Đó là key Simulator Target Bundle
với value là App ID đang cần push notification. File dạng json nhưng filename extension là .apns
, ví dụ như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token punctuation">{</span> <span class="token property">"aps"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"alert"</span> <span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"title"</span> <span class="token operator">:</span> <span class="token string">"米津玄師 MV「LOSER」"</span><span class="token punctuation">,</span> <span class="token property">"body"</span> <span class="token operator">:</span> <span class="token string">"5th Single「LOSER / ナンバーナイン」2016.9.28 RELEASE"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"category"</span> <span class="token operator">:</span> <span class="token string">"iRPCNotificationCategory"</span><span class="token punctuation">,</span> <span class="token property">"sound"</span><span class="token operator">:</span> <span class="token string">"bingbong.aiff"</span><span class="token punctuation">,</span> <span class="token property">"badge"</span><span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"videoId"</span> <span class="token operator">:</span> <span class="token string">"Dx_fKPBPYUI"</span><span class="token punctuation">,</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">"5th Single「LOSER / ナンバーナイン」2016.9.28 RELEASE"</span><span class="token punctuation">,</span> <span class="token property">"Simulator Target Bundle"</span><span class="token operator">:</span> <span class="token string">"com.thanhfnx.iRPC"</span> <span class="token punctuation">}</span> |
Như trên, chúng ta có 2 key videoId
và description
nhằm mục đích để lấy được URL video Youtube và thông tin hiển thị thêm trên custom UI. Để test push notification trên simulator, run app, accept notification permission. Sau đó, ẩn app xuống backgroun và kéo thả file .apns
vừa tạo vào simulator đang chạy.
Setup Notification Extension target infoplist
Chúng ta cần thêm các key cho notification content extension
trong Info.plist. Trong dictionary NSExtension > NSExtensionAttributes
, hãy thêm các key và value sau:
UNNotificationExtensionDefaultContentHidden
: Key này xác định khi nào thì ẩn title và body label của push notification mặc định. Trong ví dụ này, chúng ta muốn ẩn chúng, vì vậy hãy set làYES
.UNNotificationExtensionUserInteractionEnabled
: Key này xác định có enable interactive UI trên push notification hay không. Hãy set nó làYES
.UNNotificationExtensionCategory
: Key này chỉ định notification type category identifier đã register ở AppDelegate.UNNotificationExtensionInitialContentSizeRatio
: Content size ratio mặc định khi preview UI được hiển thị lần đầu, giá trị mặc định là1
.
Setup UI for the notification content preview
Công việc tiếp theo là tạo preview UI cho notification. Mở file MainInterface.storyboard
trong target notification extension và customize UI tùy theo ý muốn.
Handle receive notification and UI interaction
Mở file NotificationViewController.swift, trong file này sẽ có class NotificationViewController
là sub class của UIViewController
và conform UNNotificationContentExtension
. Method didReceive(_:)
sẽ được gọi khi nhận được push notification qua payload. Code handle các view, setup property khi nhận notification và interactive như sau:
- Import tất cả các framework cần dùng như:
AVKit
,UserNotifications
,UserNotificationsUI
,XCYoutubeKit
, vàCosmos
ở đầu file. - Khai báo tất cả các property IBOutlet cho các label, button, view, và text view.
- Khai báo các method IBAction cho các button.
- Khai báo các property lưu trạng thái của
isSubscibed
vàisFavorited
với property observer. Trong trường hợp này, text và màu của các button sẽ thay đổi dựa trên giá trị các biến bool trên. - Khai báo các constant lưu giá trị height của các view.
- Trong method
viewDidLoad
, setup các initial view state cho các button và các stack view. Review stack view và submit label sẽ bị ẩn. - Trong method
didReceive(_:)
, chúng ta sẽ nhận được content và set text cho label video title và label video description từ payload. Ngoài ra lấy đượcvideoId
và sử dụngXCDYouTubeClient
để get URL của video Youtube. - Sau khi lấy được URL, khởi tạo
AVPlayerViewController
bằng URL trêb rồi hiển thị và play video trongPlayer View
. - Khi tap vào button review, hiển thị phần stack view review. Khi tap vào button submit, ẩn stack view review và hiển thị thông báo bằng label submit. Đối với các button subscribe và favorite, khi tap vào sẽ đổi text và màu của các button này.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | <span class="token keyword">import</span> <span class="token builtin">UIKit</span> <span class="token keyword">import</span> <span class="token builtin">AVKit</span> <span class="token keyword">import</span> <span class="token builtin">UserNotifications</span> <span class="token keyword">import</span> <span class="token builtin">UserNotificationsUI</span> <span class="token keyword">import</span> <span class="token builtin">XCDYouTubeKit</span> <span class="token keyword">import</span> <span class="token builtin">Cosmos</span> <span class="token keyword">class</span> <span class="token class-name">NotificationViewController</span><span class="token punctuation">:</span> <span class="token builtin">UIViewController</span><span class="token punctuation">,</span> <span class="token builtin">UNNotificationContentExtension</span> <span class="token punctuation">{</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> playerView<span class="token punctuation">:</span> <span class="token builtin">UIView</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> reviewStackView<span class="token punctuation">:</span> <span class="token builtin">UIStackView</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> reviewButton<span class="token punctuation">:</span> <span class="token builtin">UIButton</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> videoTitleLabel<span class="token punctuation">:</span> <span class="token builtin">UILabel</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> videoDescriptionLabel<span class="token punctuation">:</span> <span class="token builtin">UILabel</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> submitLabel<span class="token punctuation">:</span> <span class="token builtin">UILabel</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> subscribeButton<span class="token punctuation">:</span> <span class="token builtin">UIButton</span><span class="token operator">!</span> <span class="token atrule">@IBOutlet</span> <span class="token keyword">weak</span> <span class="token keyword">var</span> favoriteButton<span class="token punctuation">:</span> <span class="token builtin">UIButton</span><span class="token operator">!</span> <span class="token keyword">var</span> playerController<span class="token punctuation">:</span> <span class="token builtin">AVPlayerViewController</span><span class="token operator">!</span> <span class="token keyword">let</span> standardHeight<span class="token punctuation">:</span> <span class="token builtin">CGFloat</span> <span class="token operator">=</span> <span class="token number">432</span> <span class="token keyword">let</span> reviewHeight<span class="token punctuation">:</span> <span class="token builtin">CGFloat</span> <span class="token operator">=</span> <span class="token number">658</span> <span class="token keyword">var</span> isSubscribed <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">{</span> <span class="token keyword">didSet</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>subscribeButton<span class="token punctuation">.</span>tintColor <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>isSubscribed <span class="token operator">?</span> <span class="token builtin">UIColor</span><span class="token punctuation">.</span>systemGray <span class="token punctuation">:</span> <span class="token builtin">UIColor</span><span class="token punctuation">.</span>systemBlue <span class="token keyword">self</span><span class="token punctuation">.</span>subscribeButton<span class="token punctuation">.</span><span class="token function">setTitle</span><span class="token punctuation">(</span><span class="token keyword">self</span><span class="token punctuation">.</span>isSubscribed <span class="token operator">?</span> <span class="token string">" Added"</span> <span class="token punctuation">:</span> <span class="token string">" Add"</span><span class="token punctuation">,</span> <span class="token keyword">for</span><span class="token punctuation">:</span> <span class="token punctuation">.</span>normal<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> isFavorited <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">{</span> <span class="token keyword">didSet</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>favoriteButton<span class="token punctuation">.</span>tintColor <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>isFavorited <span class="token operator">?</span> <span class="token builtin">UIColor</span><span class="token punctuation">.</span>systemGray <span class="token punctuation">:</span> <span class="token builtin">UIColor</span><span class="token punctuation">.</span>systemBlue <span class="token keyword">self</span><span class="token punctuation">.</span>favoriteButton<span class="token punctuation">.</span><span class="token function">setTitle</span><span class="token punctuation">(</span><span class="token keyword">self</span><span class="token punctuation">.</span>isFavorited <span class="token operator">?</span> <span class="token string">" Favorited"</span> <span class="token punctuation">:</span> <span class="token string">" Favorite"</span><span class="token punctuation">,</span> <span class="token keyword">for</span><span class="token punctuation">:</span> <span class="token punctuation">.</span>normal<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">viewDidLoad</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">viewDidLoad</span><span class="token punctuation">(</span><span class="token punctuation">)</span> subscribeButton<span class="token punctuation">.</span><span class="token function">setImage</span><span class="token punctuation">(</span><span class="token function">UIImage</span><span class="token punctuation">(</span>systemName<span class="token punctuation">:</span> <span class="token string">"calendar"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">for</span><span class="token punctuation">:</span> <span class="token punctuation">.</span>normal<span class="token punctuation">)</span> favoriteButton<span class="token punctuation">.</span><span class="token function">setImage</span><span class="token punctuation">(</span><span class="token function">UIImage</span><span class="token punctuation">(</span>systemName<span class="token punctuation">:</span> <span class="token string">"star"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">for</span><span class="token punctuation">:</span> <span class="token punctuation">.</span>normal<span class="token punctuation">)</span> reviewButton<span class="token punctuation">.</span><span class="token function">setImage</span><span class="token punctuation">(</span><span class="token function">UIImage</span><span class="token punctuation">(</span>systemName<span class="token punctuation">:</span> <span class="token string">"pencil"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">for</span><span class="token punctuation">:</span> <span class="token punctuation">.</span>normal<span class="token punctuation">)</span> reviewStackView<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">true</span> submitLabel<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">didReceive</span><span class="token punctuation">(</span><span class="token number">_</span> notification<span class="token punctuation">:</span> <span class="token builtin">UNNotification</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> playerController <span class="token operator">=</span> <span class="token function">AVPlayerViewController</span><span class="token punctuation">(</span><span class="token punctuation">)</span> preferredContentSize<span class="token punctuation">.</span>height <span class="token operator">=</span> standardHeight videoTitleLabel<span class="token punctuation">.</span>text <span class="token operator">=</span> notification<span class="token punctuation">.</span>request<span class="token punctuation">.</span>content<span class="token punctuation">.</span>title videoDescriptionLabel<span class="token punctuation">.</span>text <span class="token operator">=</span> notification<span class="token punctuation">.</span>request<span class="token punctuation">.</span>content<span class="token punctuation">.</span>userInfo<span class="token punctuation">[</span><span class="token string">"description"</span><span class="token punctuation">]</span> <span class="token keyword">as</span><span class="token operator">?</span> <span class="token builtin">String</span> <span class="token operator">?</span><span class="token operator">?</span> <span class="token string">""</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> videoId <span class="token operator">=</span> notification<span class="token punctuation">.</span>request<span class="token punctuation">.</span>content<span class="token punctuation">.</span>userInfo<span class="token punctuation">[</span><span class="token string">"videoId"</span><span class="token punctuation">]</span> <span class="token keyword">as</span><span class="token operator">?</span> <span class="token builtin">String</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>preferredContentSize<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token number">100</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token builtin">XCDYouTubeClient</span><span class="token punctuation">.</span><span class="token keyword">default</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getVideoWithIdentifier</span><span class="token punctuation">(</span>videoId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token keyword">weak</span> <span class="token keyword">self</span><span class="token punctuation">]</span> <span class="token punctuation">(</span>video<span class="token punctuation">,</span> error<span class="token punctuation">)</span> <span class="token keyword">in</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> <span class="token keyword">self</span> <span class="token operator">=</span> <span class="token keyword">self</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token keyword">let</span> error <span class="token operator">=</span> error <span class="token punctuation">{</span> <span class="token function">print</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>localizedDescription<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> video <span class="token operator">=</span> video <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> streamURLS <span class="token operator">=</span> video<span class="token punctuation">.</span>streamURLs <span class="token keyword">if</span> <span class="token keyword">let</span> url <span class="token operator">=</span> streamURLS<span class="token punctuation">[</span><span class="token builtin">XCDYouTubeVideoQuality</span><span class="token punctuation">.</span>medium360<span class="token punctuation">]</span> <span class="token operator">?</span><span class="token operator">?</span> streamURLS<span class="token punctuation">[</span><span class="token builtin">XCDYouTubeVideoQuality</span><span class="token punctuation">.</span>small240<span class="token punctuation">]</span> <span class="token operator">?</span><span class="token operator">?</span> streamURLS<span class="token punctuation">[</span><span class="token builtin">XCDYouTubeVideoQuality</span><span class="token punctuation">.</span><span class="token builtin">HD720</span><span class="token punctuation">]</span> <span class="token operator">?</span><span class="token operator">?</span> streamURLS<span class="token punctuation">[</span><span class="token number">18</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span><span class="token function">setupPlayer</span><span class="token punctuation">(</span>with<span class="token punctuation">:</span> url<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">func</span> <span class="token function">setupPlayer</span><span class="token punctuation">(</span>with url<span class="token punctuation">:</span> <span class="token constant">URL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> playerController <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>playerController <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> player <span class="token operator">=</span> <span class="token function">AVPlayer</span><span class="token punctuation">(</span>url<span class="token punctuation">:</span> url<span class="token punctuation">)</span> playerController<span class="token punctuation">.</span>player <span class="token operator">=</span> player playerController<span class="token punctuation">.</span>view<span class="token punctuation">.</span>frame <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>playerView<span class="token punctuation">.</span>bounds playerView<span class="token punctuation">.</span><span class="token function">addSubview</span><span class="token punctuation">(</span>playerController<span class="token punctuation">.</span>view<span class="token punctuation">)</span> <span class="token function">addChild</span><span class="token punctuation">(</span>playerController<span class="token punctuation">)</span> playerController<span class="token punctuation">.</span><span class="token function">didMove</span><span class="token punctuation">(</span>toParent<span class="token punctuation">:</span> <span class="token keyword">self</span><span class="token punctuation">)</span> player<span class="token punctuation">.</span><span class="token function">play</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">submitTapped</span><span class="token punctuation">(</span><span class="token number">_</span> sender<span class="token punctuation">:</span> <span class="token builtin">Any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">UIView</span><span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span>withDuration<span class="token punctuation">:</span> <span class="token number">0.3</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>reviewStackView<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token keyword">self</span><span class="token punctuation">.</span>submitLabel<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token keyword">self</span><span class="token punctuation">.</span>preferredContentSize<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>standardHeight <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">reviewTapped</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">UIView</span><span class="token punctuation">.</span><span class="token function">animate</span><span class="token punctuation">(</span>withDuration<span class="token punctuation">:</span> <span class="token number">0.3</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>preferredContentSize<span class="token punctuation">.</span>height <span class="token operator">=</span> <span class="token keyword">self</span><span class="token punctuation">.</span>reviewHeight <span class="token keyword">self</span><span class="token punctuation">.</span>reviewStackView<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token keyword">self</span><span class="token punctuation">.</span>reviewButton<span class="token punctuation">.</span>isHidden <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">subscribeTapped</span><span class="token punctuation">(</span><span class="token number">_</span> sender<span class="token punctuation">:</span> <span class="token builtin">Any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>isSubscribed<span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token atrule">@IBAction</span> <span class="token keyword">func</span> <span class="token function">favoriteTapped</span><span class="token punctuation">(</span><span class="token number">_</span> sender<span class="token punctuation">:</span> <span class="token builtin">Any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">self</span><span class="token punctuation">.</span>isFavorited<span class="token punctuation">.</span><span class="token function">toggle</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Build và run project, kéo thả file .apns
đã chuẩn bị ở trên vào simulator để test push notification, ta được kết quả như sau:
Conclusion
Trên đây chỉ là một ví dụ đơn giản cho việc custom các interactive push notification UI. Có rất nhiều khả năng và use case mà bạn có thể sáng tạo và ứng dụng. Khác biệt với những dòng thông báo notification đơn điệu, nhàm chán, custom interactive push notification mang lại những trải nghiệm người dùng mới, giúp tăng tương tác giữa user và app của bạn.