这算是自己做的第一个比较完整的ios的小应用程序,接触到了很多自己以前没怎么用到的东西,好像coredata,GCD,post发送请求,自定义UITableViewCell等等。
先介绍下这个小应用吧,这是新浪微博iphone版的客户端,开发环境是Xcode4.3版本以上的,ios5.1,storyboard和arc,都是比较新的东西,在低版本的Xcode是运行不了的,可以查看自己关注的人的微博,写微博,评论微博,转发,收藏等一些很基本的功能,大概就这个样子。
接下来说一下准备工作吧,首先要有一个4.3版本以上的Xcode,其实低版本的Xcode也是可以的,但是因为本人的机子是ios5.1.1的,要在上面运行程序,只能用4.3以上的版本,再就是到新浪微博开放平台申请一个应用,得到一个appkey,这是非常关键的,接下来的oauth认证需要这个appkey来实现,构思好整个应用的框架之后,就可以开始进行开发了。
现在,进入正题吧。
一、应用程序框架构造
我写应用程序的时候,会先构造一个框架,把整一个页面以及要实现的内容初步的规划出来,接着在写程序的过程有什么新的想法在加上去,下图是我整个应用程序的storyboard
二、Oauth认证
要调用新浪API,必须要有一个认证过的access_token,通过这个token才可以拿到你想要的数据,整个应用的运行成功与否,关键就是这一步了,也可以说是这是整个应用的一个难点,在做这个之前,大家最好还是先看一下开放平台上的文档,上面的解释都很清楚,最重要的是要理解这个token和如何拿到它。
在第一个视图上添加一个WebView,web认证的url为https://api.weibo.com/oauth2/authorize?client_id=appkey&response_type=token&display=mobile&redirect_uri=http://baidu.com,url中client_id是必选参数,即你自己申请的应用的appkey,response_type也是必选参数,这里设置为token,还有就是回调网址redirect_uri,我这里设置的是百度,其他的有效网址也是ok的,另外一个需要注意的是将display设为mobile,即按手机屏幕显示网页。
NSURL *applyUrl=[[NSURL alloc]initWithString:apply_url];
NSURLRequest *applyRequest=[NSURLRequest requestWithURL:applyUrl];
[webView loadRequest:applyRequest];
apply_url为上面所说的认证url,运行之后,会来到登陆界面,如下图:
输入你的微博账号和密码之后,单击授权,会跳到你所给的回调地址中,这时在跳到回调地址的过程中,会返回一些数据,返回次数有4次,这个是自己算出来的,每次得到一个回传的url就计一次数,并输出,结果得到4个输出结果,得到回传url在函数:- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType,返回的4个url中只有一个具有我们需要的access_token,所以这里要进行判断对那个拥有access_token的url进行保存,其实在输出返回url的时候不难看出4个url的不同,有access_token的必有字符”#”,所以可以根据这个来进行判断,然后保存url,并截取自己需要的access_token,具体代码如下:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
self.curUrl=request.URL.absoluteString;
for(int i=0;i<[self.curUrl length];i++)
{
char n=[self.curUrl characterAtIndex:i];
if(n=='#')
{
self.wantedUrl=self.curUrl;
break;
}
else {
self.wantedUrl=nil;
}
}
self.loadtime++;
//NSLog(@"%@,%i",self.wantedUrl,self.loadtime);
if(self.wantedUrl)
{
self.access_token=[self.wantedUrl substringWithRange:NSMakeRange(31, 32)];
self.expires_in=[self.wantedUrl substringWithRange:NSMakeRange(92, 5)];
self.uid=[self.wantedUrl substringWithRange:NSMakeRange(102, 10)];
}
NSLog(@"%@",self.access_token);
return YES;
}
access_token便是认证过的可以使用的token,拿完token之后就要load数据,一般都是跳到自己的微博首页,提醒一下各位,在跳转之前先把token存放在一个静态变量里,这里也顺便说一下吧,存为一个类的静态变量是为了以后的参数传递,静态变量的方法也不失为传递参数的一个好办法。
由于一开始没把开放平台的开发文档看透,自己花了很长的去想要怎么拿到token,最后再看了几遍文档之后,再得出上面说到的获取access_token的url,拿到token数据。
三、Coredata
这应该是第一次接触coredata吧,之前学过用sqlite来存放数据,但是觉得老是要自己建表很麻烦,如今,coredata出现了,它可以说是一个可视化的数据库,可直接在上面建表,增加属性建立联系等,这里之所以用到coredata,是用来存放token和实现账号管理的,这样就不用每次打开应用都要输入自己的账号密码,可以直接使用已授权过的token,直接进到应用的首页,这里还要注意一个问题,授权过的token的寿命是有限的,一般用来测试的token寿命为24小时,所以这里要进行一个判断,判断token是否已经过期,如果过期要重新授权,即重新回到登陆页面
在这里说一下,由于我是在中途加入coredata的,不是Xcode自动生成的,有一个办法就是随便新建一个勾选了coredata的project,将它的appdelegate里所有你之前的应用的appdelegate里没有的东西复制,这里要注意一个问题,在运行程序后,你会发现程序出错无法运行,经过自己的一番研究和对比,原来是文件名称和文件路径在作怪,如果你本身就是用coredata的话,它会帮你创建一个你的app名字的数据库和一些相关的文件索引之类的,所以,解决的办法就是在复制的代码中凡是有看到app名称的都改为你要复制到的那个app名称,问题解决。
接下来就是如何利用coredata了。
首先,在coredata.xcdatamodeld文件中建表,这里,我只需要一个Token实体存放access_token就可以了;接着就是查表赋值取值删除等数据操作。
首先是取coredata里的数据,代码如下:
MyWeiboAppDelegate *appdelegate=[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context=[appdelegate managedObjectContext];
NSEntityDescription *entityDescription=[NSEntityDescription entityForName:@"Token" inManagedObjectContext:context];
NSFetchRequest *request=[[NSFetchRequest alloc]init];
[request setEntity:entityDescription];
NSError *error;
NSArray *objects=[context executeFetchRequest:request error:&error];
先要拿到一个管理上下文即NSManagedObjectContext,然后用这个context找到Token这个实体,NSFetchRequest相当于一个查询,查询实体结构Token里的数据,再利用context将Token里的数据赋值到一个NSArray中,接下来就按照NSArray的取值方法取得access_token
以后程序每次来到这一步的时候,会先从coredata里查找有没有授权过的token,有的话可以直接使用,跳到首页,没有的话就重新回到登陆界面,输入账号密码授权,这里还有一个token是否过期的判断,我用的方法是,继续使用这个token的url,取得data,判断data是否为空,因为过期了的token,使用其url的话是得不到数据的,所数据为空,这样就好判断了,另外,还要将过期了的token从coredata里面删除掉,具体的代码如下:
for(NSDictionary *object in objects)
{
if([object valueForKey:@"access_token"])
{
[token_info setToken:[object valueForKey:@"access_token"]];
NSError *error;
NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:shouye_url,[token_info getToken]]];
NSData *data=[NSData dataWithContentsOfURL:url];
if(!data)
{
[context deleteObject:[objects objectAtIndex:0]];
if (![context save:&error]) {
exit(-1);
}
goto loop;
}
NSLog(@"%@",[token_info getToken]);
[self performSegueWithIdentifier:@"GoHome" sender:self];
}
loop:break;
}
这里用到一个for循环,是为了以后实现多用户账号管理的,现在的程序还没有实现账号管理,等以后实现了账号管理再和大家分享一下,由于只有一个token数据,所以只要一次就可以退出循环了,用一个break实现
在之前说到的拿token时,拿到token便将它存入到coredata里面,具体代码如下:
MyWeiboAppDelegate *appdelegate=[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context=[appdelegate managedObjectContext];
NSManagedObject *token=[NSEntityDescription insertNewObjectForEntityForName:@"Token" inManagedObjectContext:context];
[token setValue:self.access_token forKey:@"access_token"];
NSError *error;
if(![context save:&error])
{
NSLog(@"error!");
}
其实存与取的方法是差不多的,得到coredata里Token实体的代码是一样的,接着就是setValue而已,不要忘了,每一次执行数据操作后要保存,这样,才是真正地完成一次数据操作
重点和难点和关键步骤在以上部分已经体现得差不多了,接下来就是load数据这些重复枯燥的事情,不过,这里也要千万注意小心的,因为返回的数据是json数据,建议大家使用一下json数据查看插件,我用的是FireFox的jsonView,个人感觉还不错,其实这些东西都大同小异的,取数据的时候千万看清楚数据的位置,数据的key等,有时一个数据取错,或者因为位置算错了,导致产生一个根本不存在的数据,那么程序就会崩溃,以一段代码为例子吧:
NSError *error2;
NSURL *url=[[NSURL alloc]initWithString:[[NSString alloc]initWithFormat:shouye_url,self.access_token]];
NSData *data=[[NSData alloc]initWithContentsOfURL:url];
NSArray *ar=[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
NSArray *statuses=[ar valueForKey:@"statuses"];
for(NSDictionary *d in statuses){
NSDictionary *user =[d valueForKey:@"user"];
WeiboData *wb = [[WeiboData alloc]initWithName:[user valueForKey:@"screen_name"] andText:[d valueForKey:@"text"] andImgUrl:[user valueForKey:@"profile_image_url"]];
if([d valueForKey:@"bmiddle_pic"])
{
wb.picUrl=[d valueForKey:@"bmiddle_pic"];
}
[wb setWeiboMid:[d valueForKey:@"mid"] andReposts_count:[[d valueForKey:@"reposts_count"]intValue] andComments_count:[[d valueForKey:@"comments_count"]intValue]];
[self.allItems addObject:wb];
}
用之前得到的token获取json数据,存入数组中,然后找到自己要的那个数据就ok了,建议大家将要取的数据封装在一个类里面,用这个微博数据类来存取数据,我这里取的是微博头像,微博内容,微博名字和微博图片
四、自定义UITableViewCell
自己一开始写代码的时候,很是依赖storyboard本身提供的插件,用鼠标拉一拉,点一点就可以有很好的效果,但后来发现自己想加点东西的时候便很困难了,所以这次自己尝试了自己定义控件,第一个尝试便是UITableViewCell,其实自定义的东西一点也不难,而且好用,自己想加上什么控件就加什么,建议大家大的框图就可以用Xcode自带的东西来弄,viewcontroller里面的东西就自己定义,这样,以后加上美工什么的就不会那么麻烦了,下面就给一段代码:
UITableViewCell *cell=[[UITableViewCell alloc]init];
WeiboData *wb = [self.allItems objectAtIndex:indexPath.row];
CGSize size=[self calculateHeigh:wb.text];
UILabel *r_label=[[UILabel alloc]init];
r_label.backgroundColor=[UIColor clearColor];
UILabel *c_label=[[UILabel alloc]init];
c_label.backgroundColor=[UIColor clearColor];
UILabel *r_count=[[UILabel alloc]init];
r_count.backgroundColor=[UIColor clearColor];
UILabel *c_count=[[UILabel alloc]init];
c_count.backgroundColor=[UIColor clearColor];
r_label.text=@"转发";
r_count.text=[NSString stringWithFormat:@"%i",wb.reposts_count];
c_label.text=@"评论";
c_count.text=[NSString stringWithFormat:@"%i",wb.comments_count];
UILabel *mylabel=[[UILabel alloc]init];
mylabel.backgroundColor=[UIColor clearColor];
mylabel.frame=CGRectMake(120, 30, 150, 50);
mylabel.tag=1;
mylabel.text=wb.name;
[cell.contentView addSubview:mylabel];
//dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UILabel *mytext=[[UILabel alloc]init];
mytext.backgroundColor=[UIColor clearColor];
mytext.numberOfLines=0;
mytext.lineBreakMode=UILineBreakModeWordWrap;
mytext.tag=2;
mytext.frame=CGRectMake(20, 100, size.width-40, size.height+10);
//dispatch_async(dispatch_get_main_queue(), ^{
mytext.text=wb.text;
[cell.contentView addSubview:mytext];
这里还涉及一个问题,就是文本自适应高度和cell自适应高度,网上查了很多资料,自己总结了自己认为最好的办法,计算文本的size可以用这个函数来实现CGSize size = [string sizeWithFont:font constrainedToSize:CGSizeMake(320.0f, CGFLOAT_MAX) lineBreakMode:UILineBreakModeCharacterWrap],然后就可以跟据size的width和heigh属性去设置文本label的frame了,cell高度设置在函数:- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath里面实现,把所有的控件的高度加起来,再加上一个调整的数值,return这个高度就是cell的自定义高度,这样,cell自适应内容高度就完成了
其实,有人肯定会有一些疑问,为什么这里没用到cell的复用,是这样的,大家是知道,每条微博不一定都有微博图片,所以有些cell是不用加上UIImageView控件显示微博图片的,但是如果采用复用的话,会有一个很严重的后果,就是之前cell里的东西还会存在新的cell里,只是被你添加的控件覆盖掉而已,自己试着改几下,发现问题还是存在,复用这个问题还真让人不省心啊,后来迫不得已直接就不用用了,每次都新建一个空的新的cell,可能是自己学艺未精吧,应该是有解决的办法的,不采用复用也有很大的不好,就是每次都要新建cell,这样浪费内存和时间,这是需要改正的地方
五、GCD
又一个重点难点啊,GCD即Grend Central Dispath,就是多线程的意思,还是建议大家先看资料,理解透彻后再使用它会事半功倍,看一些操作系统的书也是有帮助的,并发,同步,异步,什么的,理解这些概念后看一些相关例子会很清晰一点,我承认自己对GCD的理解还只停留在表层的阶段,还没有真正地懂得怎么使用GCD,下面给一段cell里load图片的代码吧:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:wb.imgUrl];
wb.image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
});
dispatch_sync(dispatch_get_main_queue(), ^{
myimage.image = wb.image;
[cell.contentView addSubview:myimage];
这段大概就是一个异步里面嵌套两个同步,把下载图片和显示图片分别放在两个同步线程里面,这样写就是一次性的把图片都下载完,然后再显示出来,这段代码看起来挺简单的,就是它简单所以也出现了不少问题,最最主要的问题就是网速的问题,如果是一般SIM卡的话图片会load很久,然后很久才显示出来,还会出现头像闪换,到最后才稳定下来,建议大家测试的时候还是用wifi,或者网速更好的,这样就看不出什么问题了,而且很流畅,自己觉得应该要在两个同步里再加多东西,这个问题应该可以解决的,现在终于知道有些app为什么会出现闪退的问题了,我相信有些应该就是因为load数据的时候因为网速问题卡死了。
六、post发送请求
这次是第一次接触post发送请求这个东西,一开始还手忙脚乱的不知道该怎么弄,上网查了些资料,自己又刚好学了计算机网络这门课,这就大概知道post发送请求是什么样的一个概念了,说白了,就是将一个post方式的数据封装起来发送到要其允许的url,就这么简单,已写微博为例吧,你要把你写的东西发送出去,允许后在你的微博里就可以看到你发的微博了,需要封装的数据里包括access_token即token的值和你所发的内容status,注意,这里需要将数据编码NSUTF8StringEncoding,具体代码如下:
NSURL *url;
NSMutableURLRequest *urlRequest;
NSMutableData *postBody = [NSMutableData data];
url = [NSURL URLWithString:update_url];
urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[urlRequest setHTTPMethod:@"POST"];
NSString *string=[[NSString alloc]initWithFormat:@"&access_token=%@&status=%@",[token_info getToken],self.myLable.text];
[postBody appendData:[string dataUsingEncoding: NSUTF8StringEncoding allowLossyConversion:YES]];
[urlRequest setHTTPBody:postBody];
NSData *returnData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:nil];
if(returnData)
{
NSString *result = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);
}
else
{
NSLog(@"Error!");
}
看代码才结合开发文档里所说的,以上的代码不难理解,转发,收藏和删除微博取消收藏等道理也是一样的,不同就是在数据块那里需要添加一个微博id,用来指定要转发,收藏或者是要删除的微博等,这里就不多说了
七、整个客户端的几个运行界面
最后再做个总结吧
这次做这个微博客户端确实是让自己学到了很多东西,对自己以后的ios开发很有用处,一些平常没怎么用到的东西,这一次都有了尝试,这其中也又遇到了很多问题,还好身边有很多很厉害的人,不懂的时候可以问一下他们,其实自己觉得上网查资料,加上自己的理解和不断的尝试,总有个解决问题的办法出来
在总结一下这个应用吧,首先,有很多待解决的问题,cell的复用,GCD遇到网速不行的时候该怎么办,还有就是账号管理这一块,希望在以后的项目开发中能逐个逐个的解决,界面方面也做得不是很好,用了太多的视图,下次争取能够一个视图多用,大概就这些了吧
代码来咯,ios:新浪微博iphone客户端
提醒一下大家,在资源下载那我也有说了,请大家到新浪微博开放平台自己申请一个appkey,在程序文件中找到名为Url.h的文件,在找到apply_url,把里面的AppKey替换成你自己的appkey就可以了,不然是运行不了的。