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

Hướng dẫn tích hợp SDK iOS với bản Objective-C

Danh sách file cần thiết và các bước hướng dẫn tích hợp iTapSDK iOS với bản Objective-C 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. FBAEMKit.xcframework
  3. FBSDKCoreKit_Basics.xcframework
  4. FBSDKCoreKit.xcframework
  5. FBSDKLoginKit.xcframework
  6. FBSDKShareKit.xcframework
  7. iTapSdk.xcframework
  8. ISdkTrackier.xcframework
  9. RecapchaSdk.xcframework
  10. RXSwift.xcframework
  11. Google-Facebook .plist
  12. AppConfigDefault.plist
  13. Google-Facebook .plist.txt
  14. Podfile
  15. Project demo mẫu (example)
  16. 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.

  1. Mở file Podfile và thêm một target mới dành riêng cho extension vừa tạo:
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
platform :ios,'15.0'
target 'Unity-iPhone' do
use_frameworks!
pod 'GoogleSignIn', '7.1.0'
pod 'Firebase', '12.0.0'
pod 'FirebaseMessaging', '12.0.0'
pod 'FirebaseRemoteConfig', '12.0.0'
pod 'AppsFlyerFramework', '6.17.7'
pod 'JWT', '2.2.0'
pod 'TikTokBusinessSDK', '1.5.0'
pod 'FirebaseRemoteConfig', '12.0.0'
pod 'FirebaseCrashlytics', '12.0.0'
end
target 'UnityFramework' do
use_frameworks!
pod 'GoogleSignIn', '7.1.0'
pod 'Firebase', '12.0.0'
pod 'FirebaseMessaging', '12.0.0'
pod 'FirebaseRemoteConfig', '12.0.0'
pod 'AppsFlyerFramework', '6.17.2'
pod 'JWT', '2.2.0'
pod 'TikTokBusinessSDK', '1.5.0'
pod 'FirebaseRemoteConfig', '12.0.0'
pod 'FirebaseCrashlytics', '12.0.0'
end
target 'FirebaseNotificationServiceExtension' do
use_frameworks!
pod 'Firebase', '12.0.0'
pod 'FirebaseMessaging', '12.0.0'
end
post_install do |lib|
lib.pods_project.targets.each do |target|
if target.name == 'GoogleSignIn' || target.name == 'Firebase' || target.name == 'FirebaseRemoteConfig' || target.name == 'FirebaseMessaging' || target.name == 'AppsFlyerFramework' || target.name == 'JWT' || target.name == 'TikTokBusinessSDK'
target.build_configurations.each do |config|
config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'
end
end
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end
  1. Chạy các lệnh sau để cài đặt lại dependencies cho sạch:
Terminal window
pod cache clean --all
pod deintegrate
pod install

2.4. Cấu hình FirebaseNotificationServiceExtension

Phần tiêu đề “2.4. Cấu hình FirebaseNotificationServiceExtension”
  1. Mở file .xcworkspace (không mở .xcodeproj).
  2. Sửa nội dung file NotificationService.m của extension như sau:
#import "NotificationService.h"
@import Firebase;
@import FirebaseMessaging;
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@", self.bestAttemptContent.title];
[[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent
withContentHandler:contentHandler];
}
- (void)serviceExtensionTimeWillExpire {
[[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent
withContentHandler:self.contentHandler];
}
@end

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 với nội dung sau, để Crashlytics tự động upload debug symbol (dSYM) mỗi khi build:

Terminal window
echo "Symbols Directory"
echo "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"
#Firebase
"${PODS_ROOT}/FirebaseCrashlytics/upload-symbols" -gsp "${PROJECT_DIR}/SdkExample/GoogleService-Info.plist" -p ios "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}"

Phần này hướng dẫn cách gọi các hàm chính của SDK trong code, theo đúng thứ tự vòng đời sử dụng: khởi tạo → đăng nhập → các thao tác trong game → đăng xuất.

Trong file AppDelegate.h, import SDK và khai báo AppDelegate tuân thủ protocol SdkDelegate:

#import <UIKit/UIKit.h>
#import <iTapSdk/iTapSdk.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,SdkDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

Trong file AppDelegate.m, khởi tạo SDK ngay khi app khởi động:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[Sdk sharedInstance] initWithDelegate:self application:application launchOptions:launchOptions isProdEnv:TRUE];
[[Sdk sharedInstance] setLocationFloatingButton:20 with:100];
[[Sdk sharedInstance] setGrantEnv:YES];
return YES;
}
  • Trong lúc tích hợp và test, hãy đổi isProdEnv sang FALSE để chạy ở môi trường dev:
[[Sdk sharedInstance] initWithDelegate:self application:application launchOptions:launchOptions isProdEnv:FALSE];
  • setGrantEnv:YES — hiển thị popup xin quyền (quảng cáo, tracking…) khi vào game. Đặt setGrantEnv:NO nếu không muốn hiển thị popup này.

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 đó. Xem thêm sơ đồ luồng đăng nhập ở mục Flowchart bên dưới.

[[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 hàm loginSuccess 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 hàm logout của SdkDelegate để thông báo cho game.

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ừ store (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.

-(void)loginSuccess:(User *)user withAccessToken:(NSString *)accessToken andRefreshToken:(NSString *)refreshToken{
[[Sdk sharedInstance] listProducts:^(NSError *error, NSArray<IAPProduct*> * _Nullable result) {
}];
[[Sdk sharedInstance] showOrHideFloatingButton];
}

Gọi hàm sau để đổi ngôn ngữ hiển thị của SDK, với tham số tương ứng:

[[Sdk sharedInstance] setLanguage:@"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 cung cấp delegate getLangaugeSdk để game nhận thông báo mỗi khi ngôn ngữ được thay đổi.

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, bên trong callback loginSuccess của AppDelegate:

-(void)loginSuccess:(User *)user withAccessToken:(NSString *)accessToken andRefreshToken:(NSString *)refreshToken{
[[Sdk sharedInstance] listProducts:^(NSError *error, NSArray<IAPProduct*> * _Nullable result) {
}];
[[Sdk sharedInstance] showOrHideFloatingButton];
[[Sdk sharedInstance] showCountdown];
}

Khi người dùng bấm nút hộp quà (gift box), gọi hàm sau:

[[Sdk sharedInstance] showReceiveGift:@"1111111" withCharacterId:@"11112"];
  • serverId: Id của server.
  • characterId: Id của nhân vật (character).
[[Sdk sharedInstance] payProduct:itemProduct.productId forCharacterId:@"17782088632480" onServerId:@"s6003"];
  • itemProduct.productId: lấy từ kết quả của 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 hàm didPurchaseSuccess hoặc didPurchaseFailed của SdkDelegate đã đăng ký với SDK.

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

-(void) mappingUser:(NSString*)characterName
characterId:(NSString*)characterId
characterLevel:(NSString*)characterLevel
serverName:(NSString*)serverName
serverId:(NSString*)serverId;
// Ví dụ:
[[SdkTracking sharedInstance] mappingUser: @"name" characterId : @1 characterLevel : @1 serverName : @"test" serverId : @"1"];
  • characterName: 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 cung cấp một overload khác của hàm nhận quà, không cần truyền characterId/serverId — dùng khi bạn đã gọi mappingUser ở mục 3.10 trước đó, vì lúc này SDK đã tự biết thông tin nhân vật/server hiện tại.

Khi người dùng bấm nút hộp quà, gọi:

[[Sdk sharedInstance] showReceiveGift];

Các hàm dưới đây 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ơ đồ trên:

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

-(void) logStartGameScreen;
-(void) logEnterGamePlay;
-(void) logStartTutorial;
-(void) 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)”
-(void) logPatchStart: (NSNumber * _Nullable) sizeMB withVerrsion:(NSString * _Nullable)version withNetworkType: (NSString * _Nullable) networkType;
// sizeMB : dung lượng file patch cần tải, tính theo MB
// version : phiên bản gói cài đặt
// Ví dụ:
[[SdkTracking sharedInstance] logPatchStart: @"500" withVerrsion : @"1.0.1" withNetworkType : @"wifi"];

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”
-(void) logPatchSuccess: (NSNumber * _Nullable) durationSec withAvgSpeedKbps: (NSNumber * _Nullable) avgSpeedKbps withCdnNode :(NSString * _Nullable) cdnNode;
// durationSec : thời gian từ lúc bắt đầu tải đến khi tải xong (giây)
// avgSpeedKbps : tốc độ tải trung bình (Kbps)
// cdnNode : node CDN đã sử dụng để tải
// Ví dụ:
[[SdkTracking sharedInstance] logPatchSuccess: @"500" withAvgSpeedKbps : @"111" avgSpeedKbps : @"450" withCdnNode: @"192.168.0.2"];

Gọi khi việc tải tài nguyên bị gián đoạn hoặc thất bại.

-(void) logPatchError:(NSString * _Nullable) errorCode withRetryCount:(NSNumber * _Nullable) retryCount withFailedUrl:(NSString * _Nullable) failedUrl withCdnNode :(NSString * _Nullable) cdnNode;
// errorCode : mã lỗi
// retryCount : số lần đã retry do lỗi
// failedUrl : url bị lỗi khi tải
// cdnNode : node CDN xảy ra lỗi
// Ví dụ:
[[SdkTracking sharedInstance] logPatchError: @"500" withRetryCount: @1 withFailedUrl: @"https://cdn.game.com/patch/v120.zip" withCdnNode : @"192.168.0.2"];

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”

Gọi ngay sau khi tải xong, khi bắt đầu giải nén/cài đặt.

-(void) logExtractStarted:(NSString * _Nullable) totalSizeMb withTotalSize: (NSString * _Nullable) totalSize;
// totalSizeMb : dung lượng sau khi giải nén, tính theo MB
// totalSize : số lượng file trong patch cần giải nén (bỏ qua nếu không lấy được)
// Ví dụ:
[[SdkTracking sharedInstance] logExtractStarted: @"500" withTotalSize : @"400"];

Gọi khi giải nén xong, đã sẵn sàng vào màn hình đăng nhập.

-(void) logExtractFinished:(NSNumber * _Nullable) durationSec withResultCode:(NSString * _Nullable) resultCode;
// durationSec : thời gian từ lúc bắt đầu giải nén đến khi hoàn tất (giây)
// resultCode : mã kết quả — 0: OK, 1: Disk Full (hết dung lượng)
// Ví dụ:
[[SdkTracking sharedInstance] logExtractFinished: @100 withResultCode : @"ok"];

logCharCreateStart — Bắt đầu tạo nhân vật

Phần tiêu đề “logCharCreateStart — Bắt đầu tạo nhân vật”
-(void) logCharCreateStart: (NSString * _Nullable) serverId;
// serverId : id server
// Ví dụ:
[[SdkTracking sharedInstance] logCharCreateStart: @"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”
-(void) logCharCreateSuccess: (NSString * _Nullable) serverId withCharId:(NSString * _Nullable) charId withCharName:(NSString * _Nullable) charName withCharClass:(NSString * _Nullable) charClass withDurationSec: (NSNumber * _Nullable) durationSec;
// serverId : id server
// charId : id nhân vật
// charName : tên nhân vật
// charClass : id lớp nhân vật (class_id)
// durationSec : thời gian từ lúc bắt đầu tạo đến khi tạo thành công (giây)
// Ví dụ:
[[SdkTracking sharedInstance] logCharCreateSuccess: @"500" withVerrsion : @"1.0.1" withNetworkType : @"wifi"];

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”
-(void) logCharCreateError:(NSString * _Nullable) errorCode withCharName:(NSString * _Nullable) charName withServerId:(NSString * _Nullable) serverId;
// errorCode : mã lỗi
// charName : tên nhân vật
// serverId : id server
// Ví dụ:
[[SdkTracking sharedInstance] logCharCreateError: @"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.

-(void) logCharSelect: (NSString * _Nullable) charId withCharacterName :(NSString* _Nonnull) characterName withCharLevel:(NSString * _Nullable) charLevel;
// charId : id nhân vật
// characterName : tên nhân vật
// charLevel : level nhân vật
// Ví dụ:
[[SdkTracking sharedInstance] logCharSelect: @"1" withCharacterName : @"name " withCharLevel : @"1"];

6. Cập nhật đồng bộ thông tin theo Nghị định 147

Phần tiêu đề “6. Cập nhật đồng bộ thông tin theo Nghị định 147”
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL needShow = [[Sdk sharedInstance] isNeedShowEventProfileSync];
if (needShow) {
dispatch_async(dispatch_get_main_queue(), ^{
[[Sdk sharedInstance] showEventProfile]; // showEventProfile cần chạy trên main thread vì có UI
});
}
});
  • Nếu isNeedShowEventProfile() trả về true: mở màn hình đồng bộ thông tin (Event Profile) để người dùng cập nhật.
  • Nếu isNeedShowEventProfile() trả về false: cho phép người dùng vào game trực tiếp, không cần hiển thị màn hình này.

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.

[[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
localizedPriceGiá sản phẩm hiển thị theo locale của thiết bị
appAmountSố lượng gold/vàng người dùng nhận được khi mua gói này

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

Sdk — đối tượng SDK chính:

Thành phầnMô tả
sharedInstanceLấy singleton instance của SDK
showOrHideFloatingButton()Hiện hoặc ẩn floating button
accessTokenLấy access token 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):

10. Import certificate vào Keychain và build IPA

Phần tiêu đề “10. 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.

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.