Bỏ qua để đến nội dung
VLivev1.1.2

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.

Xem Markdown

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):

  1. AFNetworking.xcframework
  2. iTapSdk.xcframework
  3. ISdkTrackier.xcframework
  4. Google-Facebook .plist
  5. AppConfigDefault.plist
  6. Google-Facebook .plist.txt
  7. Project demo mẫu (example)
  8. 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.

Để tích hợp SDK, bạn hãy làm theo đúng thứ tự các bước dưới đây.

  • 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.

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).

  1. Mở project iOS trong Xcode.
  2. Vào File > New > Target…

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

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 UserNotifications
import Firebase
import 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.

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>

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:

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.

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 UIKit
import iTapSdk
@main
class 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 environment sang .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
}
}

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.

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()
}

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ữ
viTiếng Việt
enTiếng Anh
thTiếng Thái
zhTiế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.

Sdk.sharedInstance.showReceiveGift()

Sdk.sharedInstance.payProduct(itemProduct.productId ?? "", forCharacterId: "17782088632480", onServerId: "s6003")
  • Tham số đầu (productID): lấy từ productId của một IAPProduct trong 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.

Sdk.sharedInstance.showOrHideFloatingButton()

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.

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()

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
sizeMBDung lượng file patch cần tải, tính theo MB
withVerrsionPhiên bản gói cài đặt
withNetworkTypeLoạ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
durationSecThời gian từ lúc bắt đầu tải đến khi tải xong (giây)
withAvgSpeedKbpsTốc độ tải trung bình (Kbps)
withCdnNodeNode CDN đã sử dụng để tả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
errorCodeMã lỗi
withRetryCountSố lần đã retry do lỗi
withFailedUrlURL bị lỗi khi tải
withCdnNodeNode 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
totalSizeMbDung lượng sau khi giải nén, tính theo MB
withTotalSizeSố lượng file trong patch cần giải nén
SdkTracking.shared.logExtractFinished(durationSec: 100, withResultCode: "ok")
Tham sốÝ nghĩa
durationSecThời gian từ lúc bắt đầu giải nén đến khi hoàn tất (giây)
withResultCodeMã 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
serverIdId server
withCharIdId nhân vật
withCharNameTên nhân vật
withCharClassId lớp nhân vật (class_id)
withDurationSecThờ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")

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.

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()

IAPProduct — thông tin một sản phẩm (gói nạp):

Thuộc tínhMô tả
productIdId sản phẩm, trùng với product id khai báo trên App Store
productDescriptionMô tả sản phẩm
localizedPriceGiá sản phẩm hiển thị theo locale của thiết bị
price / currencyCode / priceLocaleGiá gốc, mã tiền tệ, locale (dùng khi cần tự format giá)
appAmountSố lượng gold/vàng người dùng nhận được khi mua gói này
amountSố vXu tương ứng

User — thông tin người dùng:

Thuộc tínhMô tả
accountIDId tài khoản người dùng
accountNameUsername của tài khoản

TransactionInfo — thông tin giao dịch thanh toán (trả về qua didPurchaseSuccess/didPurchaseFailed):

Thuộc tínhMô tả
transIdId giao dịch
productIdId sản phẩm đã mua
purchasedTokenToken xác nhận giao dịch từ App Store
paymentOrderIdId đơn hàng phía hệ thống thanh toán
confirmByServerTrạng thái xác nhận từ server

Sdk — singleton chính của SDK:

Thành phầnMô tả
Sdk.sharedInstanceSingleton của SDK
delegate: SdkDelegate?Nơi nhận các callback (login, logout, thanh toán…)
environment: Environment.prod hoặc .dev
getCurrentAccessToken() -> StringLấy access token hiện tại sau khi đăng nhập thành công

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):

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.

Làm theo các bước sau trong Xcode:

  1. Chọn đích build là Any iOS Device (arm64) thay vì simulator.

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

  1. 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.

  1. 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.

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

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

  1. 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.