源码请到Github上下载Github源码。
ios开发的基本知识:OC语言基础、Foundation基础框架、各个控件的UI界面编程(UITableView、UINavigationController、UITabBar、UIButton、UILabel、UIImageView、UIScrollView、UITextField等)、图形图像与动画、多点触摸与手势检测(响应者链、点击手势UITapGestureRecognizer、捏合手势UIPinchGestureRecognizier、旋转手势UIRotationGestureRecognizer、轻扫手势UISwipeGestureRecognizer、拖动手势UIPanGestureRecognizer)、程序的国际化、数据存储与IO、多媒体应用开发、加速度计与陀螺仪使用、多线程编程(NSThread、GCD、NSOperation与NSOperationQueue、线程安全与同步)、iOS 网络编程(XML和JSON解析、AFNetworking、ASIHTTPRequest等)定位于地图(CoreLocation、MapKit)等。
期间经历了在公司三个多月的iOS开发实习,学习了很多iOS 开发的实战经验,帮助开发了某APP的地图定位、骑行轨迹、、骑行数据记录、照片滤镜美化等功能的开发。从八月份起,笔者开始根据自己的一个想法开发一款真正的自己的APP,以深化开发的基础及增加实际的iOS开发经验。
APP的主要功能有三点:
1.美颜相机;2.相册分类管理;3.基于相册分类、用户标签及用户行为的人物画像的社交功能。
APP的设计思路是:
1.首先用户注册时必须上传一张自己的人脸照片,照片的人脸识别使用腾讯优图的优图人脸检测API,并且每个用户设置自己的兴趣标签;
2.其次获取每个用户手机相册里的照片,使用腾讯优图的图片标签识别API,将用户的照片上传至优图服务器,返回用户照片信息进行分类,建立手机照片分类管理工具,并且在APP后台建立云相册,便于用户照片管理与保存;
3.开发自定义的相机,实现美颜等功能;
4.根据用户的信息、标签、照片信息在后台建立用户画像,基于用户的地理位置,推荐位置相近并且画像相似的用户给本用户,方便进一步的社交。
笔者目前的开发过程如下,后期将不断更新:
1.自定义TabBarVC,建立四个Nav及对应的四个rootVC:四个主页面的VC。这里需要对于初次开发者来说,需要注意的就是UITableBarController的每个标签的控制器最好使用UINavigationController,因为如果直接使用UIViewController的话,后面二级之后的页面仍然会有UITableBar在底部,一般的APP不需要这样的功能,当然,比如像知乎就使用了这个功能;还有就是关于状态栏及导航栏的问题。(可参考:导航控制器与标签控制器,状态栏导航栏问题,iOS导航控制器和标签栏控制器的结合)。代码如下:
{if (!self){self=[super init];}SZFaceFriendVC* faceFriendVC=[[SZFaceFriendVC alloc] init];UINavigationController* faceFriendNav=[[UINavigationController alloc] initWithRootViewController:faceFriendVC];UITabBarItem* faceFriendItem=[[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"FaceFriend", nil) image:nil tag:0];faceFriendNav.tabBarItem=faceFriendItem;faceFriendNav.navigationBar.translucent = NO;SZPhotoGraphVC* photoGraphVC=[[SZPhotoGraphVC alloc] init];UINavigationController* photoGraphNav=[[UINavigationController alloc] initWithRootViewController:photoGraphVC];UITabBarItem* photoGraphItem=[[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"TakePhoto", nil) image:nil tag:0];photoGraphNav.tabBarItem=photoGraphItem;photoGraphNav.navigationBar.translucent = NO;SZAlbumVC* albumVC=[[SZAlbumVC alloc] init];UINavigationController* albumNav=[[UINavigationController alloc] initWithRootViewController:albumVC];UITabBarItem* albumItem=[[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"Album", nil) image:nil tag:0];albumNav.tabBarItem=albumItem;albumNav.navigationBar.translucent = NO;SZMeVC* meVC=[[SZMeVC alloc] init];UINavigationController* meNav=[[UINavigationController alloc] initWithRootViewController:meVC];UITabBarItem* meItem=[[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"Me", nil) image:nil tag:0];meNav.tabBarItem=meItem;meNav.navigationBar.translucent = NO;// 设置UITabBar背景图片self.viewControllers = [NSArray arrayWithObjects:faceFriendNav,photoGraphNav,albumNav,meNav, nil];return self;
}
实现后界面如下:
2.运用AVFoudation框架进行自定义相机模块开发,开发一个用于显示界面及处理界面点击等事件的控制器SZPhotoGraphVC,然后建立一个基于AVFoudation框架的实现拍照、拍摄、切换前后镜头等功能的控制器SZRecordManager。实现效果如下:
3.相册模块开发,通过ALassetsLibrary读取所有手机照片,建立所有照片的URL数组;调用优图物体检测API,根据获取返回的信息解析出每张照片的标签数组及其置信度,分析获取每张照片的最可信标签获取照片的类别,建立所有手机照片的标签数组并同时建立不重复的标签数组;写了包含相册界面的视图及标签视图,通过不重复的标签建立相册视图显示在相册控制器相册视图与相册控制器间通过代理实现点击事件的响应,完成各个相册展示具体相片的相簿控制器,点击相册进入相簿控制器视图,协议方法在相册控制器实现进入相簿页面并显示本相簿所有照片(可参考:ios获取相册使用照片,优图图片标签检测API)。实现效果如下:
4.在相簿控制器集成友盟的分享功能,可分享至微博等(可参考:友盟U-Share SDK集成API)。实现效果如下:
5.在“我的页面”完成了个人信息视图,通过CLLocation框架接口写了一个获取当前城市名称的模型,在“我的页面”的个人信息显示页面通过键值观察模式监听地理位置的改变并作出修改,并通过单击我的头像进入信息修改控制器页面,在修改页面与显示页面间通过通知实现个人信息的传递,当在修改页面修改昵称、头像、个性签名等信息后发布控制器注册成为通知中心的观察者,获取到通知信息并修改显示页面信息(可参考:ios_CoreLocation实现定位当前城市,iOS 中KVC、KVO、NSNotification、delegate 总结及区别)。实现效果如下:
6.改变了相册页面控制器和我的信息修改控制器的载入方式,将控制器设为导航控制器的根控制器,直接的push方式改为presented导航控制器的方式,避免了tabBar仍在下一级控制器页面的出现。
7.采用偏好设置(沙盒里面的Library文件夹里的Preference文件存储,NSUserDefault)进行个人信息的数据持久化存储,显示页面与编辑页面直接通过该文件进行数据交互(可参考:iOS的五种数据持久化方案)。
8.在“脸友”页面集成融云的联系人列表,聊天功能。实现效果如下(可参考:融云iOS IMKit SDK 开发指南):
9.在“脸友”页面自定义后台推荐的脸友信息显示视图(仿照探探),为视图添加滑动手势识别,通过块动画显示视图滑动,当滑动范围超过阈值,显示下一个脸友的信息视图,并且在本视图移除之前的离开屏幕的过程与滑动手势速度关联,速度越快,移除越快,否则则将本脸友视图返回屏幕原位置;自定义获取后台推荐的脸友信息模型,通过网络获取脸友信息,并且预加载三个脸友叠加显示,之后没移除一个脸友则再添加一个脸友视图。代码如下:
-(void)viewDidLoad
{[super viewDidLoad];[self initUI];//为脸图片添加拖动手势处理UIPanGestureRecognizer* panGesture=[[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panFaceProcess:)];panGesture.minimumNumberOfTouches=1;panGesture.maximumNumberOfTouches=2;self.infoFaceView.userInteractionEnabled=YES;self.panGesture=panGesture;[self.infoFaceView addGestureRecognizer:self.panGesture];self.view.backgroundColor=[UIColor whiteColor];[self.navigationController.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:19],NSFontAttributeName,[UIColor blackColor],NSForegroundColorAttributeName, nil]];//1.自定义导航条的title视图UILabel *titleTextLabel = [[UILabel alloc] initWithFrame: CGRectMake((SCREENWIDTH-120)/2, 0, 120, 50)];titleTextLabel.backgroundColor = [UIColor clearColor];titleTextLabel.textColor=[UIColor blackColor];titleTextLabel.textAlignment=NSTextAlignmentCenter;[titleTextLabel setFont:[UIFont systemFontOfSize:19.0]];[titleTextLabel setText:NSLocalizedString(@"FaceFriend", nil)];self.navigationItem.titleView=titleTextLabel;//2.导航条获取联系人列表左按钮UIBarButtonItem* contactsBtn=[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Chat", nil) style:UIBarButtonItemStylePlain target:self action:@selector(enterList)];self.navigationItem.leftBarButtonItem=contactsBtn;//3.导航条进入朋友圈右按钮UIBarButtonItem* friendCircleBtn=[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"FriendCircle", nil) style:UIBarButtonItemStylePlain target:self action:@selector(enterChat)];self.navigationItem.rightBarButtonItem=friendCircleBtn;
}-(void)initUI
{//4.infoFaceViewW=SCREENWIDTH-20;infoFaceViewH=SCREENWIDTH-20+94;FriendNum=3;index=2;self.infoFaceViewArray=[NSMutableArray arrayWithCapacity:FriendNum];self.facesArray=[[NSArray alloc] initWithObjects:@"face1.jpg",@"face2.jpg",@"face3.jpg", nil];for (NSUInteger i=0; i<FriendNum; i++){//网络请求获取推荐人的信息SZFriendInfoModel* friendInfoModel=[[SZFriendInfoModel alloc] initWithFace:[UIImage imageNamed:[self.facesArray objectAtIndex:i]] name:@"Andy" sex:@"female" signature:[NSString stringWithFormat:@"aceFriend is a good APP! %ld",i]];SZInfoFaceView* infoFaceView=[[SZInfoFaceView alloc] initWithFrame:CGRectMake(10, 10,infoFaceViewW , infoFaceViewH)image:friendInfoModel.faceImage name:friendInfoModel.nickName sex:friendInfoModel.sex signature:friendInfoModel.signature];[self.infoFaceViewArray addObject:infoFaceView];[self.view addSubview:[self.infoFaceViewArray lastObject]];}self.infoFaceView=[self.infoFaceViewArray lastObject];//6.底部likeOrNoViewCGFloat likeOrNoViewY=infoFaceViewH+10;CGFloat likeOrNoViewH=SCREENHEIGTH-likeOrNoViewY-44-64;//NSLog(@"likeOrNoViewH:%f",likeOrNoViewH);UIView* likeOrNoView=[[UIView alloc] initWithFrame:CGRectMake(0,likeOrNoViewY, SCREENWIDTH,likeOrNoViewH)];likeOrNoView.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:@"likeOrNo.jpg"]];[self.view addSubview:likeOrNoView];//7.喜欢及不喜欢按钮CGFloat margin=50;CGFloat marginY=15;CGFloat btnH=likeOrNoViewH-2*marginY;CGFloat btnW=btnH;CGFloat marginX=(SCREENWIDTH-2*btnW-margin)*0.5;UIButton* unLikeBtn=[[UIButton alloc] initWithFrame:CGRectMake(marginX, marginY, btnW, btnH)];[unLikeBtn setBackgroundImage:[UIImage imageNamed:@"unLike.jpg"] forState:UIControlStateNormal];[likeOrNoView addSubview:unLikeBtn];UIButton* LikeBtn=[[UIButton alloc] initWithFrame:CGRectMake(marginX+btnW+margin, marginY, btnW, btnH)];[LikeBtn setBackgroundImage:[UIImage imageNamed:@"like.jpg"] forState:UIControlStateNormal];[likeOrNoView addSubview:LikeBtn];
}
//拖动脸图片处理方法
-(void)panFaceProcess:(UIPanGestureRecognizer*)panGesture
{if (panGesture.state==UIGestureRecognizerStateBegan){//记录开始的触电位置startX=[panGesture locationInView:self.view].x;startY=[panGesture locationInView:self.view].y;//记录原始的图片中心initCenterX=panGesture.view.center.x;initCenterY=panGesture.view.center.y;}CGFloat translationX=[panGesture translationInView:self.view].x;CGFloat translationY=[panGesture translationInView:self.view].y;panGesture.view.center=CGPointMake(panGesture.view.center.x+translationX, panGesture.view.center.y+translationY);[panGesture setTranslation:CGPointZero inView:self.view];//添加滑移效果if (panGesture.state==UIGestureRecognizerStateEnded){CGFloat velocityX=[panGesture velocityInView:self.view].x;CGFloat velocityY=[panGesture velocityInView:self.view].y;//1. 计算速度向量的长度CGFloat magnitude = sqrtf((velocityX * velocityX) + (velocityY * velocityY));//2.如果速度向量小于200,那就会得到一个小于1的小数,那么滑行会很短CGFloat slideMult = magnitude / 200;//3.基于速度和速度因素计算一个终点//返回原位置float slideFactor = 0.03 * slideMult;//如果手势结束并且图片移动的距离大于阈值时,移除本图片View,并显示下一张endX=[panGesture locationInView:self.view].x;endY=[panGesture locationInView:self.view].y;CGFloat totalTranslation=sqrtf(powf((endX-startX), 2)+powf((endY-startY), 2));//NSLog(@"slideFactor%F,velocityX=%f,startX=%f,endX=%f,totalTranslation:%f",slideFactor,velocityX,startX,endX,totalTranslation);if (totalTranslation>150){// 放大速度,手势速度越快,消失越快CGPoint finalPoint=CGPointMake(panGesture.view.center.x+velocityX*10, panGesture.view.center.y+velocityY*10);//动画移除本图片View[UIView animateWithDuration:0.5 animations:^{panGesture.view.center=finalPoint;} completion:^(BOOL finished){[self.infoFaceView removeFromSuperview];for (int i=FriendNum-1; i>=0; i--){if (i>0){[self.infoFaceViewArray setObject:[self.infoFaceViewArray objectAtIndex:i-1] atIndexedSubscript:i];}else{//网络请求获取推荐人的信息SZFriendInfoModel* friendInfoModel=[[SZFriendInfoModel alloc] initWithFace:[UIImage imageNamed:[self.facesArray objectAtIndex:(index++)%FriendNum]] name:@"Andy" sex:@"female" signature:[NSString stringWithFormat:@"aceFriend is a good APP! %ld",(index++)%FriendNum]];SZInfoFaceView* infoFaceView=[[SZInfoFaceView alloc] initWithFrame:CGRectMake(10, 10,infoFaceViewW , infoFaceViewH)image:friendInfoModel.faceImage name:friendInfoModel.nickName sex:friendInfoModel.sex signature:friendInfoModel.signature];[self.infoFaceViewArray setObject:infoFaceView atIndexedSubscript:i];}}self.infoFaceView=[self.infoFaceViewArray lastObject];[self.view addSubview:self.infoFaceView];[self.infoFaceView addGestureRecognizer:self.panGesture];}];}//如果没有移除,则显示滑移效果返回原位置else{CGPoint finalPoint = CGPointMake(initCenterX,initCenterY);//5.使用UIView动画使view滑动到终点[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{panGesture.view.center = finalPoint;} completion:nil];}}
}
实现效果如下:
10.在“相册”页面的第一个相册实现照片的幻灯片式自动轮播,主要使用了UIImageView图片视图的照片组动画,设置UIImageView的animationImages属性。
11.使用UITabeView重写”我”的页面,添加如账号密码使用帮助等基本条条目;点击账号密码,进入登录控制器页面,主要包括账号登录,第三方登录,注册机忘记密码入口,并完成注册及忘记密码两个控制器。网络通信采用ASIHTTPRequest第三方框架,采用邮箱或者电话号码注册的方式,用户账户信息及密码等采用第三方的SFHFKeychainUtils工具,运用钥匙串进行安全存储(可参考:IOS开发之记录用户登陆状态,iOS开发——密码存储之keychain的使用,iOS开发中——如何保存用户敏感信息(用户名和密码等信息))。代码如下:
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service
{return [NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword,(id)kSecClass,service, (id)kSecAttrService,service, (id)kSecAttrAccount,(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,nil];
}+ (void)save:(NSString *)service data:(id)data
{//Get search dictionaryNSMutableDictionary *keychainQuery = [self getKeychainQuery:service];//Delete old item before add new itemSecItemDelete((CFDictionaryRef)keychainQuery);//Add new object to search dictionary(Attention:the data format)[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];//Add item to keychain with the search dictionarySecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}+ (id)load:(NSString *)service
{id ret = nil;NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];//Configure the search setting//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];CFDataRef keyData = NULL;if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr){@try{ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];} @catch (NSException *e){NSLog(@"Unarchive of %@ failed: %@", service, e);} @finally{}}if (keyData)CFRelease(keyData);return ret;
}+ (void)deleteKeyData:(NSString *)service
{NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];SecItemDelete((CFDictionaryRef)keychainQuery);
}
实现效果如下:
截至10月初开发到这里,后续将继续更新。欢迎大家来交流和学习指导。笔者微信为:lrh312825660。
简单总结了一下技术点和难点如下:
1.状态栏及导航栏设置问题;
2.自定义相机(AVFoundation框架;
3.手机照片全获取(ALAssetsLibrary);
4.代理、通知、键值观察;
5.UIScrollView等可滚动视图在导航控制器的显示,系统自动为显示内容下拉64点(导航条及状态栏高度);
6.通过CLLocation框架获取当前城市,通过反地理编码解析出当前的城市;
7.Preference文件存储用户数据;
8.滑动手势处理;
9.使用ASIHttpRequest框架进行网络通信;
10.使用第三方的SFHFKeychainUtils工具,运用keychain的方式对用户信息密码等敏感信息进行本地数据的持久化。
难点:
1.优图接口的调用,异步请求及返回数据的解析,获取使用相片的最可信标签及所有照片的不重复标签;
2.自定义相机及后续优化;
3.通过代理、键值观察、通知实现页面间的通信。
4.滑动手势处理脸友视图的移除,加载,及动画。
最后,也是最主要的就是:源码请到Github上下载Github源码。