【網絡動畫效果】
衆所周知,包括但不限於網絡處理,很多使用APP的時機都需要展示Loading或者Toast提示的形式來提升用戶的交互體驗。
- 自定義Loading類:是繼承UIActivityIndicatorView的子類。簡化創建與管理。指定了佈局與樣式等。對外暴露創建與消失方法。
#import "LJZLoading.h"
@implementation LJZLoading
- (instancetype)initWithActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style
{
self = [super initWithActivityIndicatorStyle:style];
if(self){
self.hidesWhenStopped = YES;
self.frame = CGRectMake(0, 0, 150, 150);
self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, [UIScreen mainScreen].bounds.size.height/2);
self.backgroundColor = [UIColor blackColor];
self.alpha = 0.5;
[[[UIApplication sharedApplication] keyWindow] addSubview:self];
[self startAnimating];
}
return self;
}
- (void)StopShowLoading
{
[self stopAnimating];
[self removeFromSuperview];
}
@end
- 自定義Toast類”LJZToast“:設計爲以單例模式調用,控制內容與展示時長,在固定位置展示出提示信息。其頭文件如下:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ToastLabel : UILabel
- (void)setMessageText:(NSString *)text;
@end
@interface LJZToast : NSObject
@property (nonatomic,strong)ToastLabel *toastLabel;
+ (instancetype)shareInstance;
- (void)ShowToast:(NSString *)message duration:(CGFloat)duration;
@end
- Toast的實現上,UILabel子類的樣式自定義,dispatch_once執行靜態單例。dispatch_after控制展示animateWithDuration實現消失動畫,具體實現如下:
#import "LJZToast.h"
@implementation ToastLabel
- (instancetype)init
{
self = [super init];
if(self){
self.layer.cornerRadius = 8;
self.layer.masksToBounds = YES;
self.backgroundColor = [UIColor blackColor];
self.numberOfLines = 0;
self.textAlignment = NSTextAlignmentCenter;
self.textColor = [UIColor whiteColor];
self.font = [UIFont systemFontOfSize:15];
}
return self;
}
- (void)setMessageText:(NSString *)text
{
[self setText:text];
self.frame = CGRectMake(0, 0, 150, 50);
self.center = CGPointMake([UIScreen mainScreen].bounds.size.width/2, 150);
}
@end
@implementation LJZToast
+ (instancetype)shareInstance
{
static LJZToast *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[LJZToast alloc] init];
});
return singleton;
}
- (instancetype)init
{
self = [super init];
if(self){
self.toastLabel = [[ToastLabel alloc] init];
}
return self;
}
- (void)ShowToast:(NSString *)message duration:(CGFloat)duration
{
if([message length] == 0){
return;
}
[_toastLabel setMessageText:message];
_toastLabel.alpha = 0.8;
[[[UIApplication sharedApplication] keyWindow] addSubview:_toastLabel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self changeTime];
});
}
- (void)changeTime
{
[UIView animateWithDuration:0.2f animations:^{
_toastLabel.alpha = 0;
}completion:^(BOOL finished) {
[_toastLabel removeFromSuperview];
}];
}
@end
【移動端網絡請求】
- 類NSURLSession
對於iOS而言,網絡編程主要依賴兩種方式:NSURLSession以及NSURLConnection。在iOS9.0之後,NSURLConnection過期不再使用。本系統使用NSURLSession來完成客戶端的網絡模塊。NSURLSession具有以下優勢
(1)NSURLSession支持http2.0協議。
(2) NSURLSession在下載任務時會直接寫入沙盒和tem文件夾中,不會放入內存避免內存暴漲。
(3)NSURLSession提供來全局的session並且可以統一配置,每一個實例也可單獨配置。
(4)NSURLSession下載支持斷點續傳,可以取消暫停繼續,使用多線程異步處理下載。 - 類NSURLSessionTask
NSURLSessionTask是一個抽象類,如圖4-3展示,使用時根據具體需求使用子類。
- 請求過程與解析
在遊戲中,用戶需要在登陸,註冊,上傳成績,以及查看排行等地方進行網絡請求,NSURLSession的使用非常簡便,根據會話對象創建一個請求Task然後執行,請求過程
結合Loading與Toast的使用,APP中主要的網絡請求實現如下:
- 登錄請求與處理部分
- (void)HandleLogin
{
[self.view endEditing:YES];
NSString *username = self.account.text;
NSString *password = self.password.text;
//輸入合法性檢驗
if (username.length == 0 || password.length == 0){
[[LJZToast shareInstance] ShowToast:@"用戶名與密碼不能爲空!" duration:1];
return;
}
LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/login?username=%@&password=%@",username,password]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil) {
NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
NSLog(@"reponse data:%@",responseInfo);
if([responseInfo isEqualToString:@"enable"]){
NSUserDefaults *userDefault= [NSUserDefaults standardUserDefaults];
[userDefault setObject:username forKey:@"user_name"];
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"登錄成功!" duration:1];
[self dismissViewControllerAnimated:YES completion:nil];
});
} else{
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"登錄失敗,請檢查用戶名與密碼!" duration:1];
});
}
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
});
NSLog(@"server error:%@",error);
}
}];
[dataTask resume];
}
- 註冊處理
- (void)HandleRegister
{
NSString *username = self.account.text;
NSString *password = self.password.text;
if (username.length == 0 || password.length == 0){
[[LJZToast shareInstance] ShowToast:@"用戶名與密碼不能爲空!" duration:1];
return;
}else if (username.length > 15 || password.length >15){
[[LJZToast shareInstance] ShowToast:@"用戶名與密碼不超過15位!" duration:1];
return;
}
//輸入合法性檢驗
LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/register?username=%@&password=%@",username,password]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil) {
NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
NSLog(@"reponse data:%@",responseInfo);
if([responseInfo isEqualToString:@"enable"]){
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"註冊成功!" duration:1];
[self dismissViewControllerAnimated:YES completion:nil];
});
} else{
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"該名字已被註冊!" duration:1];
});
}
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
});
NSLog(@"server error:%@",error);
}
}];
[dataTask resume];
}
- 請求排行榜數據
- (void)GetPlayersInfo
{
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSString *username = [userDefault objectForKey:@"user_name"];
NSString *isLogin;
if (username == nil){
_playerAccoutRank.text = @"-";
_playerAccoutScore.text = @"-";
_playerAccoutName.text = @"未登陸";
isLogin = @"no";
} else{
_playerAccoutName.text = [NSString stringWithFormat:@"%@",username];
isLogin = @"yes";
}
//請求排行榜數據
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/rankInfo?username=%@&islogin=%@",username,isLogin]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil) {
NSDictionary *responseInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSLog(@"reponse data:%@",responseInfo);
if ([isLogin isEqualToString:@"yes"]){
_playerRank = responseInfo[@"self_rank"];
_playerScore = responseInfo[@"self_score"];
dispatch_async(dispatch_get_main_queue(), ^{
[self ShowSelfInfo];
});
}
_firstPlayerName = responseInfo[@"p1_name"];
_secondPlayerName = responseInfo[@"p2_name"];
_thirdPlayerName = responseInfo[@"p3_name"];
_fourthPlayerName = responseInfo[@"p4_name"];
_fifthPlayerName = responseInfo[@"p5_name"];
_sixthPlayerName = responseInfo[@"p6_name"];
_firstPlayerScore = responseInfo[@"p1_score"];
_secondPlayerScore = responseInfo[@"p2_score"];
_thirdPlayerScore = responseInfo[@"p3_score"];
_fourthPlayerScore = responseInfo[@"p4_score"];
_fifthPlayerScore = responseInfo[@"p5_score"];
_sixthPlayerScore = responseInfo[@"p6_score"];
dispatch_async(dispatch_get_main_queue(), ^{
[self ShowRankInfo];
});
}else{
[[LJZToast shareInstance] ShowToast:@"請檢查網絡!" duration:1];
NSLog(@"server error:%@",error);
}
}];
[dataTask resume];
}
- 在遊戲結束界面上傳新成績
- (void)handleUpload
{
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
NSString *username = [userDefault objectForKey:@"user_name"];
if (username == nil){
[[LJZToast shareInstance] ShowToast:@"未登陸不可上傳成績" duration:1];
} else{
LJZLoading *loadingView = [[LJZLoading alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://39.98.152.99:80/upLoadScore?username=%@&score=%ld",username,self.resultScore]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if(error == nil) {
NSString *responseInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];//接受字符串
NSLog(@"reponse data:%@",responseInfo);
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:[NSString stringWithFormat:@"%@",responseInfo] duration:1];
});
}else{
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView StopShowLoading];
[[LJZToast shareInstance] ShowToast:@"上傳成績失敗,請檢查網絡!" duration:1];
});
NSLog(@"server error:%@",error);
}
}];
[dataTask resume];
}
}
【其他優化】
- 合理的地方加入主線程保護:主要爲UI改動。
dispatch_async(dispatch_get_main_queue(), ^{
//要做的事情
});
- 注意釋放內存
//移除通知
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
//場景中。。。
- (void)die
{
[self.pipeTimer invalidate];
[self.toolTimerOne invalidate];
[self.toolTimerTwo invalidate];
[self.toolTimerThree invalidate];
[self.bgmPlayer stop];
self.bgmPlayer = nil;
[self changeToResultSceneWithScore:self.score];
}
- 耗時與持續工作放入後臺線程運行。
- 在關鍵的工作節點與事件觸發中打出Log信息,按一定規則打印,便於測試。
【未來展望】
作爲遊戲APP算是搭建比較完善了。遊戲組件的抽離也有利於後續的擴展遊戲功能。存在包括不限以下的ToDo:
- 遊戲角色單一,是否可添加遊戲角色的選擇。
- 遊戲賬號貨幣的積累,開放遊戲商城功能。
- 遊戲內容或者截圖分享?
- 道具與障礙的有待多元化?
- 支持多人運動飛行?