题外话
基于市场运营针对老版本H5与本地混合app(偏h5,大概只有登录注册找回密码用户那块等是本地)无法很好的推广,公司领导决定重新改版,统一用本地模式。并且要求大概20个工作日完成。所以这段时间确实很忙,主要因素有以下几点,总体就是不确定和空白:
- 整个项目原型不确定,不断的开会,讨论。原型断断续续的出来了,功能还是不确定,问领导,等领导答复...
- UI这边也是,迟迟等领导答复。9.2号开始,真正的到9月中旬,才开始有了初步的原型和UI.
- 后台接口也是空白,边调边做。
- 项目总的人力资源:iOS 2人(1新人),安卓2人(1新人),后台接口1人(外援2人,负责接口的人同时负责后台pc),UI 1人,产品原型 1人。
后期会陆续补充整个项目的框架,以及用到的一些第三方库及使用,以及一些费时间点。
正题
iOS这边整个项目进度:大概功能已开发完成,测试那边也大概的走了一遍。解决bug中。上几个图,嘿嘿😋
以下就记录下自己觉得比较重要的知识点(个人观点哈,大咖请绕行,😀)
1. tableview 多个textfield, 键盘遮挡问题
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//监听键盘出现和消失,解决键盘遮挡最后textfield问题
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark 键盘出现
-(void)keyboardWillShow:(NSNotification *)note
{
CGRect keyBoardRect=[note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
self.tableview.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64 - keyBoardRect.size.height);
}
#pragma mark 键盘消失
-(void)keyboardWillHide:(NSNotification *)note
{
self.tableview.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64);
}
2. oc与js简单交互
场景:邀请页面加载H5,如下邀请好友页面,思路分解:
- oc给js传值,js那边负责显示邀请人数和奖励金额
- js调用本地,查看邀请记录 标识处理
- js调用本地,调用本地友盟分享
核心代码(相应代码位置自己调一下)如下:
#import <WebKit/WebKit.h>
#import <JavaScriptCore/JavaScriptCore.h>
@property (strong, nonatomic) JSContext *context;
#pragma mark - 邀请好友页面 uiwebview代理
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
self.loadCount --;
//oc call js,oc给js传值
self.context = [[JSContext alloc] init];
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 打印异常
self.context.exceptionHandler =
^(JSContext *context, JSValue *exceptionValue)
{
context.exception = exceptionValue;
NSLog(@"%@", exceptionValue);
};
[self.webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"inviteInfo('%@','%@');",
self.inviteNumber,self.inviteEarn]];
__weak BannerHrefVC *weakself = self;
self.context[@"handleShare"] =
^(NSString *title,NSString *text)
{
dispatch_async(dispatch_get_main_queue(), ^(void){
NSLog(@"%@ %@",title,text);
weakself.shareTitle = title;
weakself.shareText = text;
[weakself setupUMShare];
});
};
self.context[@"goFriendList"] =
^(void)
{
dispatch_async(dispatch_get_main_queue(), ^(void){
[weakself.navigationController pushViewController:[[FriendsListVC alloc]initWithTitle:@"好友列表" ] animated:NO];
});
};
}
3. 上传头像
场景:个人中心,从本地拍照或者选择相册里面照片上传到服务器,并更新。特别注意content-type的设置(设置不对,会发生一系列的错误,最折磨人的是后台那边能收到图片,但是打不开,这个时候不仅跟这个参数有关,后台那边接收方式也有关系)如下图:
核心代码如下:
iOS客户端
AFURLResponseSerialization.h 的init里面
#pragma -mark 我这边直接改的源文件,不建议这样改,自己在用的地方相应设置下就好
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"multipart/form-data",@"text/plain", nil];
最好自己用的地方如下设置:
AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
//https
session.securityPolicy = [self customSecurityPolicy];
session.securityPolicy.allowInvalidCertificates = YES;
session.responseSerializer = [AFJSONResponseSerializer serializer];
session.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"multipart/form-data",@"text/plain", nil];
session.requestSerializer = [AFJSONRequestSerializer serializer];
#pragma -mark
#pragma -mark tableview delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
//设置头像
[self alterHeadPortrait];
}
}
- (void)alterHeadPortrait{
//解决iOS8在调用系统相机拍照时,会有一两秒的停顿,然后再弹出UIImagePickConroller的问题
if([[[UIDevice
currentDevice] systemVersion] floatValue]>=8.0) {
self.modalPresentationStyle=UIModalPresentationOverCurrentContext;
}
//初始化提示框
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
//按钮:从相册选择,类型:UIAlertActionStyleDefault
[alert addAction:[UIAlertAction actionWithTitle:@"从相册选择" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//初始化UIImagePickerController
UIImagePickerController *PickerImage = [[UIImagePickerController alloc]init];
//获取方式1:通过相册(呈现全部相册),UIImagePickerControllerSourceTypePhotoLibrary
//获取方式2,通过相机,UIImagePickerControllerSourceTypeCamera
//获取方法3,通过相册(呈现全部图片),UIImagePickerControllerSourceTypeSavedPhotosAlbum
PickerImage.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
//允许编辑,即放大裁剪
PickerImage.allowsEditing = YES;
//自代理
PickerImage.delegate = self;
//页面跳转
[self presentViewController:PickerImage animated:YES completion:nil];
}]];
//按钮:拍照,类型:UIAlertActionStyleDefault
[alert addAction:[UIAlertAction actionWithTitle:@"拍照" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action){
/**
其实和从相册选择一样,只是获取方式不同,前面是通过相册,而现在,我们要通过相机的方式
*/
UIImagePickerController *PickerImage = [[UIImagePickerController alloc]init];
//获取方式:通过相机
PickerImage.sourceType = UIImagePickerControllerSourceTypeCamera;
PickerImage.allowsEditing = YES;
PickerImage.delegate = self;
[self presentViewController:PickerImage animated:YES completion:nil];
}]];
//按钮:取消,类型:UIAlertActionStyleCancel
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
#pragma mark
#pragma mark PickerImage完成后的代理方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
//定义一个newPhoto,用来存放我们选择的图片。
UIImage *newPhoto = [info objectForKey:@"UIImagePickerControllerEditedImage"];
self.ivHead.image = newPhoto;
//上传头像到服务器
[self reqUpdateHeader:newPhoto];
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark
#pragma mark 上传头像到服务器
- (void)reqUpdateHeader:(UIImage *)image{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSString *strURL = [NSString stringWithFormat:@"http://test.helloan.cn/mobile/rest/uploadPhoto/savePhoto/1?token=%@",
[PersonalCenterData sharedInstance].userInfo.token];
[manager POST:strURL parameters:nil
constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
NSData *fileData = UIImageJPEGRepresentation(image, 0.1);
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", str];
[formData appendPartWithFileData:fileData name:@"fileName" fileName:fileName mimeType:@"image/jpg"];
} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"%@",responseObject);
if ([[responseObject objectForKey:@"code"] isEqualToString:Response_OK]) {
[PersonalCenterData sharedInstance].userInfo.logoUrl = [responseObject objectForKey:@"logoUrl"];
}
[self.view makeToast:[responseObject objectForKey:@"message"]];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
java后台
DiskFileItemFactory factory = new DiskFileItemFactory();
//设置工厂的缓冲区的大小,当上传的文件大小超过缓冲区的大小时,就会生成一个临时文件存放到指定的临时目录当中。
factory.setSizeThreshold(1024*100);//设置缓冲区的大小为100KB,如果不指定,那么缓冲区的大小默认是10KB
//创建一个文件上传解析器
ServletFileUpload upload = new ServletFileUpload(factory);
//解决上传文件名的中文乱码
upload.setHeaderEncoding("UTF-8");
//设置上传单个文件的大小的最大值,目前是设置为1024*1024字节,也就是1MB
upload.setFileSizeMax(1024*1024);
//设置上传文件总量的最大值,最大值=同时上传的多个文件的大小的最大值的和,目前设置为10MB
// upload.setSizeMax(1024*1024*10);
//4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
List<FileItem> list = upload.parseRequest(request);
4. label自动换行问题
网上查挺多的,这里只是记录下,以后好查阅😀
UILabel *lb = [[UILabel alloc]init];
lb.numberOfLines = 0;
lb.lineBreakMode = NSLineBreakByWordWrapping;
5. label设置部分字体颜色
其实可以跟上面写在一起的,但是懒得改下面的序列号了,真的好懒啊,莫怪😀
UILabel *lb = [[UILabel alloc]initWithFrame:CGRectMake(ContentX, 0, kScreenWidth-ContentX, kRowHeight+10)];
lb.textColor = [UIColor grayColor];
NSString *str1 = @"密码为6-15个字符,建议字母数字和特殊字符混合,登录密码必须和交易密码不同。新用户交易密码为登录密码。";
NSMutableAttributedString *str = [[NSMutableAttributedString alloc]
initWithString:str1];
// //设置字号
// [str addAttribute:NSFontAttributeName value:font range:range];
//设置文字颜色
NSRange range =[str1 rangeOfString:@"新用户交易密码为登录密码。"];
[str addAttribute:NSForegroundColorAttributeName value:kColorHeadPerson range:range];
lb.attributedText = str;
lb.numberOfLines = 0;
lb.lineBreakMode = NSLineBreakByWordWrapping;
lb.font = kFontText;
[head addSubview:lb];
6. 动态获取cell高度
待写
7. 重写导航左侧返回按钮
如下代码也对导航和底部tab的显示和隐藏做了处理
#pragma -mark viewLife
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.automaticallyAdjustsScrollViewInsets = NO;
self.navigationController.navigationBar.barTintColor = kColorHeadPerson;
//半透明状态
self.navigationController.navigationBar.translucent = NO;
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
//设置文字前景色
[self.navigationController.navigationBar setTitleTextAttributes:
@{NSFontAttributeName:[UIFont systemFontOfSize:22],
NSForegroundColorAttributeName:[UIColor whiteColor]}];
//重写返回按钮
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"back_normal"]
style:UIBarButtonItemStylePlain
target:self
action:@selector(buttonBack)];
self.hud = [[MBProgressHUD alloc]initWithView:self.view];
self.hud.delegate = self;
self.hud.mode = MBProgressHUDModeIndeterminate;
self.hud.label.text=@"加载中,请稍后!";
self.hud.backgroundColor =[UIColor lightGrayColor];
[self.view addSubview:self.hud];
}
- (void)buttonBack
{
[self.navigationController popViewControllerAnimated:NO];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
self.tabBarController.tabBar.hidden = YES;
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.tabBarController.tabBar.hidden = NO;
}
8. 程序延迟执行
场景:操作完之后,例如修改密码完成之后,先toast,之后延时3秒再进行页面跳转。若不延时,toast显示是看不到的。
网上后来看到有三种方式,大概为:performSelector,nstimer,gcd. 我这边用的是gcd
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//your code
});
9. 从普通vc到tabitem中的一个vc
场景:从实名认证成功页面(该页面为从个人中心也所push的,此页上有个去投资按钮)回到产品页(如下图)
核心代码如下:
//转到产品列表页
- (void)goInvest
{
self.tabBarController.selectedIndex = 1;
[self.navigationController popToRootViewControllerAnimated:NO];
}
10. 列表上拉弹回问题
- (void)viewDidLoad {
[super viewDidLoad];
self.tableview = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
self.tableview.dataSource = self;
self.tableview.delegate = self;
self.tableview.scrollEnabled = YES;
[self.view addSubview:self.tableview];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
self.tableview.contentSize =CGSizeMake(0, kRowHeight*5+ButtonHeight +40+10);
}
11. 充值(调用第三方宝付接口)
充值接口目前这个版本走的其实都是web,api貌似要贵好多,以后版本公司可能会改用api。多一句,web真的好慢,其实充值这么慢,无形中会流失掉一部分想投资的用户。
#pragma -mark
#pragma -mark 充值获取交易号
- (void)doCharge
{
NSLog(@"doCharge");
NSString *token = [PersonalCenterData sharedInstance].userInfo.token;
if (!token) {
[self.view makeToast:NoLoginMsg];
return;
}
[[PersonalCenterLogicManager sharedInstance]postChargeWithToken:token amount:[self.tfAmount.text intValue] success:^(NSDictionary *dic) {
if ([[dic objectForKey:@"code"] isEqualToString:Response_OK]) {
NSString *tradeNo =[dic objectForKey:@"tradeNo"];
if (tradeNo) {
//跳转到第三方宝付充值
BaoFooPayController*web = [[BaoFooPayController alloc] init];
web.PAY_TOKEN = tradeNo;
web.delegate = self;
[self presentViewController: web animated:YES completion:^{ }];
}
}
else{
[self.view makeToast:[dic objectForKey:@"message"]];
}
} failure:^(NSError *err) {
[self.view makeToast:ServerError];
}];
}
#pragma mark - BaofooDelegate
-(void)callBack:(NSString*)params
{
NSLog(@"返回的参数是:%@",params);
if ([params rangeOfString:@"支付成功"].location != NSNotFound) {
//条件为真,表示字符串searchStr包含一个自字符串substr
ChargeSuccessVC *vc = [[ChargeSuccessVC alloc]initWithTitle:@"充值成功"];
vc.chargeAmount = self.tfAmount.text;
vc.bankNumber = self.bankInfo.bankNumber;
[self.navigationController pushViewController:vc animated:NO];
}else
{
//条件为假,表示不包含要检查的字符串
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"支付结果:%@",params] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}
}
还需待写的有
1 银行选择
2 二级联动城市选择
3 顶部滚动等
连续上7天的班,脑壳都快炸了,回家好好休息😝,看到这里的你,愿你周末愉快!