Hướng dẫn tích hợp SDK Swift cho iOS
Danh sách file cần thiết và các bước hướng dẫn tích hợp SDK Swift cho iOS vào dự án.
1. Danh sách file cần thiết
Phần tiêu đề “1. Danh sách file cần thiết”Trước khi tích hợp, bạn cần chuẩn bị đầy đủ các file sau (do đội ngũ VTVLive cung cấp):
AFNetworking.xcframeworkiTapSdk.xcframeworkISdkTrackier.xcframework- Google-Facebook
.plist AppConfigDefault.plist- Google-Facebook
.plist.txt - Project demo mẫu (example)
- File
.mobileprovision
Sau khi test xong ở môi trường dev, chúng tôi sẽ cung cấp key riêng cho môi trường production.
2. Các bước tích hợp SDK
Phần tiêu đề “2. Các bước tích hợp SDK”Để tích hợp SDK, bạn hãy làm theo đúng thứ tự các bước dưới đây.
2.1. Thêm framework và library vào project
Phần tiêu đề “2.1. Thêm framework và library vào project”- Trong project chính (target app), chọn chế độ Embed & Sign cho các library đã liệt kê ở trên.

- Trong UnityFramework (nếu game build từ Unity), chọn chế độ Do Not Embed cho các library — vì các library này đã được embed ở target chính, tránh bị nhân đôi.

2.2. Thêm Notification Service Extension
Phần tiêu đề “2.2. Thêm Notification Service Extension”Extension này giúp SDK xử lý và hiển thị đúng nội dung push notification (kể cả khi có ảnh, dữ liệu tuỳ biến từ Firebase).
- Mở project iOS trong Xcode.
- Vào File > New > Target…

- Trong danh sách template, tìm và chọn Notification Service Extension.
- Bấm Next, đặt tên target — ví dụ
FirebaseNotificationServiceExtension— rồi bấm Finish. - Khi Xcode hỏi có muốn kích hoạt scheme mới không, chọn Activate.

2.3. Thêm package dependencies
Phần tiêu đề “2.3. Thêm package dependencies”Thêm các package cần thiết (ví dụ Firebase) vào project qua Swift Package Manager:

2.4. Cấu hình FirebaseNotificationServiceExtension
Phần tiêu đề “2.4. Cấu hình FirebaseNotificationServiceExtension”Mở file NotificationService.swift của extension và sửa nội dung như sau:
import UserNotificationsimport Firebaseimport FirebaseMessaging
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent { FIRMessaging.extensionHelper().populateNotificationContent(bestAttemptContent, withContentHandler: contentHandler) } }
override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } }}Sau bước này, cấu trúc project trên Xcode sẽ hiển thị như hình dưới:

Ngoài ra:
- Để bật đăng nhập bằng Apple, bạn cần bật capability Sign in with Apple cho target chính.
- Để nhận được push notification, bạn cần thêm capability Push Notifications, đồng thời bật Background Fetch và Remote Notifications trong mục Background Modes.
2.5. Thêm cấu hình Facebook vào Info.plist
Phần tiêu đề “2.5. Thêm cấu hình Facebook vào Info.plist”Sau khi đã có Facebook App ID và Facebook Client Token (liên hệ VTVLive để lấy thông tin cho ứng dụng của bạn), thêm đoạn cấu hình sau vào file Info.plist của project. Các giá trị bên dưới chỉ là ví dụ minh hoạ — hãy thay bằng thông tin thực tế của bạn:
<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>fb24125138790480742</string> </array> </dict> <dict> <key>CFBundleURLSchemes</key> <array> <string>com.googleusercontent.apps.630962979382-dko6grp0h2m3qnka8tndbj20rtcm495f</string> </array> </dict> </array> <key>CFBundleVersion</key> <string>1.3.4</string> <key>FacebookAppID</key> <string>24125138790480742</string> <key>FacebookClientToken</key> <string>5a78f323394b24d1f97c425876c24aca</string> <key>FacebookDisplayName</key> <string>Sdk Test Facebook</string> <key>LSApplicationQueriesSchemes</key> <array> <string>fbapi</string> <string>fbapi20130214</string> <string>fbapi20130410</string> <string>fbapi20130702</string> <string>fbapi20131010</string> <string>fbapi20131219</string> <string>fbapi20140410</string> <string>fbapi20140116</string> <string>fbapi20150313</string> <string>fbapi20150629</string> <string>fbapi20160328</string> <string>fbauth</string> <string>fb-messenger-share-api</string> <string>fbauth2</string> <string>fbshareextension</string> </array> <key>NSAdvertisingAttributionReportEndpoint</key> <string>https://appsflyer-skadnetwork.com/</string> <key>NSUserTrackingUsageDescription</key> <string>This identifier will be used to deliver personalized ads to you.</string>2.6. Cấu hình Firebase Crashlytics
Phần tiêu đề “2.6. Cấu hình Firebase Crashlytics”Thêm một Run Script Phase vào Build Phases của target chính để Crashlytics tự động upload debug symbol (dSYM) mỗi khi build:

3. Tích hợp các hàm SDK
Phần tiêu đề “3. Tích hợp các hàm SDK”SDK cung cấp một singleton Sdk.sharedInstance cho toàn bộ tính năng chính, và một singleton riêng SdkTracking.shared cho các hàm tracking sự kiện. Phần này hướng dẫn theo đúng vòng đời sử dụng: khởi tạo → đăng nhập → các thao tác trong game → đăng xuất.
3.1. Khởi tạo SDK
Phần tiêu đề “3.1. Khởi tạo SDK”Trong AppDelegate.swift, import SDK, khai báo AppDelegate tuân thủ protocol SdkDelegate, và khởi tạo SDK ngay khi app khởi động:
import UIKitimport iTapSdk
@mainclass AppDelegate: UIResponder, UIApplicationDelegate, SdkDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Sdk.sharedInstance.initWithDelegate(self, application: application, launchOptions: launchOptions, environment: .prod) Sdk.sharedInstance.setLocationFloatingButton(width: 20, height: 100) return true }
// Bắt buộc: chuyển tiếp URL callback cho Facebook / Google Sign-In func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { return Sdk.sharedInstance.application(application, open: url, options: options) }}- Trong lúc tích hợp và test, hãy đổi
environmentsang.dev:
Sdk.sharedInstance.initWithDelegate(self, application: application, launchOptions: launchOptions, environment: .dev)Cài đặt các callback của SdkDelegate (tất cả đều optional, chỉ implement những cái bạn cần) trong một extension của AppDelegate:
extension AppDelegate {
func sdk(_ sdk: Sdk, loginSuccess user: User, accessToken: String, refreshToken: String) { // Đăng nhập thành công — user.accountID, user.accountName }
func sdk(_ sdk: Sdk, logout user: User) { // Đăng xuất thành công }
func sdk(_ sdk: Sdk, didPurchaseSuccess transactionInfo: TransactionInfo) { // Thanh toán thành công — transactionInfo.productId, transactionInfo.transId... }
func sdk(_ sdk: Sdk, didPurchaseFailed transactionInfo: TransactionInfo, purchaseError: String?) { // Thanh toán thất bại }
func sdk(_ sdk: Sdk, getLanguage language: String) { // Ngôn ngữ SDK vừa thay đổi }
func sdkCloseLoginView() { // Màn hình đăng nhập vừa được đóng }}3.2. Đăng nhập (Login)
Phần tiêu đề “3.2. Đăng nhập (Login)”Luôn gọi hàm login() trước khi game bắt đầu. SDK sẽ tự hiển thị dialog đăng nhập nếu đây là lần đầu người dùng vào game, hoặc tự động refresh token nếu người dùng đã từng đăng nhập trước đó.
Sdk.sharedInstance.login()
Nếu mọi thứ thành công, dialog đăng nhập sẽ hiển thị như hình trên.

Khi người dùng đăng nhập thành công, thông tin user sẽ được trả về qua callback sdk(_:loginSuccess:accessToken:refreshToken:) của SdkDelegate mà bạn đã đăng ký với SDK.
3.3. Đăng xuất (Logout)
Phần tiêu đề “3.3. Đăng xuất (Logout)”Sdk.sharedInstance.logout()Khi đăng xuất thành công, SDK sẽ gọi lại callback sdk(_:logout:) để thông báo cho game. Ngoài ra còn có Sdk.sharedInstance.forceLogout() — đăng xuất ngay lập tức mà không gọi API huỷ refresh token phía server (dùng khi phiên đăng nhập đã hết hạn phía server).
3.4. Lấy danh sách sản phẩm (List products)
Phần tiêu đề “3.4. Lấy danh sách sản phẩm (List products)”Lấy danh sách sản phẩm (gói nạp) từ App Store. Bạn nên gọi hàm này ngay sau khi đăng nhập thành công, vì cần dùng giá sản phẩm để gửi lên hệ thống tracking khi người dùng thanh toán.
func sdk(_ sdk: Sdk, loginSuccess user: User, accessToken: String, refreshToken: String) { Sdk.sharedInstance.listProducts { error, products in // products: [IAPProduct]? } Sdk.sharedInstance.showOrHideFloatingButton()}3.5. Đổi ngôn ngữ (Set Language)
Phần tiêu đề “3.5. Đổi ngôn ngữ (Set Language)”Gọi hàm sau để đổi ngôn ngữ hiển thị của SDK:
Sdk.sharedInstance.setLanguage(input: "vi")Các mã ngôn ngữ được hỗ trợ:
| Mã | Ngôn ngữ |
|---|---|
vi | Tiếng Việt |
en | Tiếng Anh |
th | Tiếng Thái |
zh | Tiếng Trung |
SDK cũng gọi lại callback sdk(_:getLanguage:) của SdkDelegate mỗi khi ngôn ngữ được thay đổi (bao gồm cả lần đầu khởi tạo).

3.6. Hiển thị đếm ngược (Show countdown)
Phần tiêu đề “3.6. Hiển thị đếm ngược (Show countdown)”Để hiển thị giao diện đếm ngược như hình dưới, gọi showCountdown() sau khi đăng nhập hoàn tất:

func sdk(_ sdk: Sdk, loginSuccess user: User, accessToken: String, refreshToken: String) { Sdk.sharedInstance.listProducts { error, products in } Sdk.sharedInstance.showOrHideFloatingButton() Sdk.sharedInstance.showCountdown()}Bạn có thể kiểm tra trước xem sự kiện đếm ngược có đang khả dụng không bằng Sdk.sharedInstance.isCountdownAvailable() -> Bool.
3.7. Nhận quà (Receive Gift)
Phần tiêu đề “3.7. Nhận quà (Receive Gift)”Sdk.sharedInstance.showReceiveGift()
3.8. Thanh toán (Make payment)
Phần tiêu đề “3.8. Thanh toán (Make payment)”Sdk.sharedInstance.payProduct(itemProduct.productId ?? "", forCharacterId: "17782088632480", onServerId: "s6003")- Tham số đầu (
productID): lấy từproductIdcủa mộtIAPProducttrong kết quảlistProductsở mục 3.4. forCharacterId: Id nhân vật trong game.onServerId: Id server.
Kết quả thanh toán (thành công hoặc thất bại) sẽ được trả về tương ứng qua callback sdk(_:didPurchaseSuccess:) hoặc sdk(_:didPurchaseFailed:purchaseError:) của SdkDelegate.
3.9. Hiện/ẩn floating button (Show dashboard)
Phần tiêu đề “3.9. Hiện/ẩn floating button (Show dashboard)”Sdk.sharedInstance.showOrHideFloatingButton()3.10. Ánh xạ thông tin nhân vật (Map User)
Phần tiêu đề “3.10. Ánh xạ thông tin nhân vật (Map User)”Gọi hàm này để gửi thông tin nhân vật hiện tại của người dùng lên SDK, phục vụ cho tracking và các tính năng liên quan đến nhân vật/server (bao gồm cả showReceiveGift() ở mục 3.7):

Sdk.sharedInstance.mappingUser( "name", characterId: "1", characterLevel: "1", serverName: "test", serverId: "1")- Tham số đầu (không có nhãn): tên nhân vật.
characterId: Id của nhân vật.characterLevel: Level hiện tại của nhân vật.serverName: Tên server.serverId: Id server.
SDK còn có Sdk.sharedInstance.levelUpUser(_:characterId:characterLevel:serverName:serverId:vip:) — gọi mỗi khi nhân vật lên level hoặc lên VIP, để hệ thống tracking ghi nhận mốc đạt được.
4. Hàm ghi log tracking (SdkTracking)
Phần tiêu đề “4. Hàm ghi log tracking (SdkTracking)”Các hàm dưới đây nằm trên singleton SdkTracking.shared (khác với Sdk.sharedInstance), dùng để gửi sự kiện tracking về hệ thống, giúp đo lường hành vi người dùng trong toàn bộ vòng đời chơi game — từ mở app, tải patch, tạo nhân vật đến vào game.
Chú thích màu trong sơ đồ Flowchart gốc:
- Màu xám: các bước xử lý phía Game.
- Màu vàng: các điểm Game cần gọi hàm của SDK.
Vui lòng xem thêm mục “Login or Start Game” trong Flowchart để biết vị trí chính xác cần đặt các hàm log bên dưới trong luồng chạy của game.
SdkTracking.shared.logStartGameScreen()SdkTracking.shared.logEnterGamePlay()SdkTracking.shared.logStartTutorial()SdkTracking.shared.logCompleteTutorial()logEnterGamePlay()
Phần tiêu đề “logEnterGamePlay()”Tracking sự kiện người dùng vào màn hình gameplay, ngay sau khi bấm nút “Enter Game”.

logPatchStart — Bắt đầu tải tài nguyên (patch)
Phần tiêu đề “logPatchStart — Bắt đầu tải tài nguyên (patch)”SdkTracking.shared.logPatchStart(sizeMB: 500, withVerrsion: "1.0.1", withNetworkType: "wifi")| Tham số | Ý nghĩa |
|---|---|
sizeMB | Dung lượng file patch cần tải, tính theo MB |
withVerrsion | Phiên bản gói cài đặt |
withNetworkType | Loại mạng ("wifi", "4g"…) |
logPatchSuccess — Tải tài nguyên hoàn tất
Phần tiêu đề “logPatchSuccess — Tải tài nguyên hoàn tất”SdkTracking.shared.logPatchSuccess(durationSec: 450, withAvgSpeedKbps: 111, withCdnNode: "192.168.0.2")| Tham số | Ý nghĩa |
|---|---|
durationSec | Thời gian từ lúc bắt đầu tải đến khi tải xong (giây) |
withAvgSpeedKbps | Tốc độ tải trung bình (Kbps) |
withCdnNode | Node CDN đã sử dụng để tải |
logPatchError — Tải tài nguyên thất bại
Phần tiêu đề “logPatchError — Tải tài nguyên thất bại”SdkTracking.shared.logPatchError(errorCode: "500", withRetryCount: 1, withFailedUrl: "https://cdn.game.com/patch/v120.zip", withCdnNode: "192.168.0.2")| Tham số | Ý nghĩa |
|---|---|
errorCode | Mã lỗi |
withRetryCount | Số lần đã retry do lỗi |
withFailedUrl | URL bị lỗi khi tải |
withCdnNode | Node CDN xảy ra lỗi |
logExtractStarted — Bắt đầu giải nén tài nguyên
Phần tiêu đề “logExtractStarted — Bắt đầu giải nén tài nguyên”SdkTracking.shared.logExtractStarted(totalSizeMb: "500", withTotalSize: "400")| Tham số | Ý nghĩa |
|---|---|
totalSizeMb | Dung lượng sau khi giải nén, tính theo MB |
withTotalSize | Số lượng file trong patch cần giải nén |
logExtractFinished — Giải nén hoàn tất
Phần tiêu đề “logExtractFinished — Giải nén hoàn tất”SdkTracking.shared.logExtractFinished(durationSec: 100, withResultCode: "ok")| Tham số | Ý nghĩa |
|---|---|
durationSec | Thời gian từ lúc bắt đầu giải nén đến khi hoàn tất (giây) |
withResultCode | Mã kết quả — "ok"/0: OK, 1: Disk Full |
logCharCreateStart — Bắt đầu tạo nhân vật
Phần tiêu đề “logCharCreateStart — Bắt đầu tạo nhân vật”SdkTracking.shared.logCharCreateStart(serverId: "100")logCharCreateSuccess — Tạo nhân vật thành công
Phần tiêu đề “logCharCreateSuccess — Tạo nhân vật thành công”SdkTracking.shared.logCharCreateSuccess( serverId: "100", withCharId: "1001", withCharName: "test", withCharClass: "1", withDurationSec: 5)| Tham số | Ý nghĩa |
|---|---|
serverId | Id server |
withCharId | Id nhân vật |
withCharName | Tên nhân vật |
withCharClass | Id lớp nhân vật (class_id) |
withDurationSec | Thời gian từ lúc bắt đầu tạo đến khi tạo thành công (giây) |
logCharCreateError — Tạo nhân vật thất bại
Phần tiêu đề “logCharCreateError — Tạo nhân vật thất bại”SdkTracking.shared.logCharCreateError(errorCode: "NAME_DUPLICATED", withCharName: "test", withServerId: "1")logCharSelect — Chọn nhân vật
Phần tiêu đề “logCharSelect — Chọn nhân vật”Gọi hàm này ngay sau khi người dùng chọn server và nhân vật để vào game.
SdkTracking.shared.logCharSelect("1", withCharacterName: "name", withCharLevel: "1")5. Cập nhật đồng bộ thông tin theo Nghị định 147
Phần tiêu đề “5. Cập nhật đồng bộ thông tin theo Nghị định 147”Khác với bản Objective-C cũ (2 hàm isNeedShowEventProfileSync + showEventProfile gọi thủ công), bản Swift gộp thành một hàm duy nhất checkShow147, và SDK tự hiển thị màn hình đồng bộ thông tin nếu cần — bạn chỉ cần gọi và (tuỳ chọn) đọc kết quả qua completion:
Sdk.sharedInstance.checkShow147 { needVerify in // needVerify == true: SDK đã tự mở màn hình đồng bộ thông tin (Event Profile) // needVerify == false: không cần làm gì thêm, có thể cho người dùng vào game}Nên gọi ngay trong callback loginSuccess, trước khi cho vào màn hình chính của game.
6. Gợi ý đánh giá ứng dụng (Review App)
Phần tiêu đề “6. Gợi ý đánh giá ứng dụng (Review App)”Dùng tính năng này để gợi ý người dùng đánh giá (review) ứng dụng ngay trong app, thông qua hộp thoại đánh giá chuẩn của Apple (SKStoreReviewController).
Sdk.sharedInstance.requestReview()
7. Các lớp/kiểu dữ liệu chính trong SDK
Phần tiêu đề “7. Các lớp/kiểu dữ liệu chính trong SDK”IAPProduct — thông tin một sản phẩm (gói nạp):
| Thuộc tính | Mô tả |
|---|---|
productId | Id sản phẩm, trùng với product id khai báo trên App Store |
productDescription | Mô tả sản phẩm |
localizedPrice | Giá sản phẩm hiển thị theo locale của thiết bị |
price / currencyCode / priceLocale | Giá gốc, mã tiền tệ, locale (dùng khi cần tự format giá) |
appAmount | Số lượng gold/vàng người dùng nhận được khi mua gói này |
amount | Số vXu tương ứng |
User — thông tin người dùng:
| Thuộc tính | Mô tả |
|---|---|
accountID | Id tài khoản người dùng |
accountName | Username của tài khoản |
TransactionInfo — thông tin giao dịch thanh toán (trả về qua didPurchaseSuccess/didPurchaseFailed):
| Thuộc tính | Mô tả |
|---|---|
transId | Id giao dịch |
productId | Id sản phẩm đã mua |
purchasedToken | Token xác nhận giao dịch từ App Store |
paymentOrderId | Id đơn hàng phía hệ thống thanh toán |
confirmByServer | Trạng thái xác nhận từ server |
Sdk — singleton chính của SDK:
| Thành phần | Mô tả |
|---|---|
Sdk.sharedInstance | Singleton của SDK |
delegate: SdkDelegate? | Nơi nhận các callback (login, logout, thanh toán…) |
environment: Environment | .prod hoặc .dev |
getCurrentAccessToken() -> String | Lấy access token hiện tại sau khi đăng nhập thành công |
8. Sơ đồ luồng (Flowchart)
Phần tiêu đề “8. Sơ đồ luồng (Flowchart)”Sơ đồ tổng quan:

Sơ đồ trên mô tả luồng “Login or Start Game” — thứ tự các bước và vị trí cần gọi các hàm SDK/tracking đã giới thiệu ở các mục trên.
Sơ đồ luồng thanh toán (Payment):

9. Import certificate vào Keychain và build IPA
Phần tiêu đề “9. Import certificate vào Keychain và build IPA”Chúng tôi sẽ cung cấp cho bạn các certificate cần thiết để build file .ipa, bao gồm: apn_certificate, dev_certificate, dis_certificate, development provisioning profile và production provisioning profile.

Bước 1 — Import certificate vào Keychain: double-click (hoặc kéo thả) file .p12 để thêm vào Keychain Access.

Sau khi import thành công, bạn sẽ thấy certificate xuất hiện trong Keychain như hình dưới:

Bước 2 — Import provisioning profile vào Xcode: double-click file provisioning profile (hoặc kéo thả vào Xcode) để Xcode tự nhận diện.


Cách export file IPA để upload lên App Store
Phần tiêu đề “Cách export file IPA để upload lên App Store”Làm theo các bước sau trong Xcode:
- Chọn đích build là Any iOS Device (arm64) thay vì simulator.

- Vào menu Product > Archive để Xcode build và đóng gói archive.

- Sau khi archive xong, cửa sổ Organizer sẽ tự mở và hiển thị bản archive vừa tạo. Chọn Distribute App.

- Chọn phương thức phân phối phù hợp (ví dụ App Store Connect hoặc Ad Hoc) và làm theo các bước wizard của Xcode.

- Trước khi export, nhớ kiểm tra kỹ mục aps-environment trong entitlements phải là
production(không phảidevelopment), nếu không push notification sẽ không hoạt động trên bản release.

- Sau khi export xong, chọn Show in Finder để mở thư mục chứa file
.ipa.

- Chuột phải vào file
.ipa(hoặc file.xcarchive) và chọn Show Package Contents để lấy file dSYM bên trong nếu cần gửi debug symbol.


