iOS学习之网易新闻简易Demo
简易NewsDemo代码分析
界面布局就不多说了.效果图:(自定义了三套Cell,最后一套Cell是页面最下方的"正在加载中..."Cell,图三.)
主要分析工程目录和流程.
第一:Helper中的负责请求数据的网络引擎类.
网络请求类NetworkEngine主要完成数据请求的任务.用来简化控制器中的代码.将数据单独存放.
实现步骤分析:
.h文件中:
1.分析完成网络请求需要:①请求的网址 ②请求的参数 ③请求的方式(GET或者POST).
那么将这三个定义为属性.请求参数只有两种,所以定义为枚举类型.然后将请求数据和结束请求数据写成两个方法.
//
// NetworkEngine.h
// NewsDemo #import <Foundation/Foundation.h>
/**
* NetworkEngine 是网络引擎类,完成数据的网络请求操作.
*/ @class NetworkEngine;
@protocol NetworkEngineDelegate <NSObject>
//协议中添加两个方法,一个对应请求数据成功的方法,一个对应请求数据失败的方法
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data;
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error;
@end //请求方式(枚举类型)
typedef enum RequsetMethod {
RequsetMethodGET,
RequsetmethodPOST
}RequestMethod; @interface NetworkEngine : NSObject
@property (nonatomic, copy) NSString *linkName; //请求服务器地址
@property (nonatomic, retain) NSDictionary *parmDic; //参数字典,存储所有的请求参数.
@property (nonatomic, assign) RequestMethod requestMethod; //请求方式 @property (nonatomic, assign) id<NetworkEngineDelegate> delegate; //指定代理 //开始请求数据
- (void)startDownloadData;
//结束请求数据
- (void)stopDownloadData; @end
.m文件中:
2.有了服务器址,参数和请求方式,下面开始请求数据.但是前提是要把服务器地址装化为NSURL格式.这里就要根据请求方式来判断是否需要将参数拼接到服务器地址后面了.
GET方式的请求需要在服务器地址后,用?隔开,然后加上请求的参数.例如服务器地址为:
//API 网络接口
#define kNewsAPI @"http://m2.qiushibaike.com/article/list/text"
那么GET请求就需要加上参数变为
http://m2.qiushibaike.com/article/list/text?count=30&page=%25d&AdID=14314020462881C8509990
参数为一个字典, @{count:30,page:25,ADID:14314020462881C8509990}
下面就是判断请求方式和实现参数拼接的代码实现了,这里不详述了.
3.将服务器地址转为NSURL对象之后,下一步创建请求.由于不确定是GET还是POST,这里就用通用的创建请求方式,也是POST的请求方式
//GET创建请求方式(不可变URLRequest)
NSURLRequest *request = [NSURLRequset requestWithURL:]; //POST创建请求方式(可变MutableURLRequest)
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:];
4.如果是POST方式,还要设置请求体(就是GET方式的URL中?后面的参数)和请求方式.
[request setHTTPBody:[[self appendStringByParDic] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
5.下一步创建连接对象(使用代理方式),将连接对象定义成属性,便于在结束方法中访问.
连接对象创建完毕,就可以连接服务器请求数据,(这里定义一个属性NSMutableData *mData,存储从服务器放回的所有数据.) 这里还要实现代理中的四个方法.
①:didReceiveResponse:收到服务器响应,这时候为mData开辟空间.
②:didReceiveData:开始从服务器接收数据.(一点一点传送),这时候需要进行数据拼接.
③:connectionDidFinishLoading:接收数据完毕. 关键点:这时候NetworkEngine的获取数据的工作已经完成了,它需要能够将数据发送给控制器.怎么发送呢?自然要使用代理了.
在.h文件中定义协议,并且添加方法.然后指定代理.
//协议中添加两个方法,一个对应请求数据成功的方法,一个对应请求数据失败的方法
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data;
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error;
并且在.m文件中的connectionDidFinishLoading方法中,给代理指定执行时机.(这本来是使用代理的最后一步,这里提前说.).
//通知代理执行协议中请求数据成功的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestSuccessWithData:)]) {
[self.delegate networkEngine:self requestSuccessWithData:self.mData];
}
到此,网络请求完成.
// NetworkEngine.m
// NewsDemo #import "NetworkEngine.h" @interface NetworkEngine ()<NSURLConnectionDataDelegate>
@property (nonatomic, retain) NSURLConnection *connection; //存储连接对象
@property (nonatomic, retain) NSMutableData *mData; //存储服务器返回的所有数据
@end @implementation NetworkEngine //开始请求数据
- (void)startDownloadData {
//1.创建NSURL对象
NSURL *url = [NSURL URLWithString:[self urlString]];
//2.创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (self.requestMethod == RequsetmethodPOST) {
//如果是POST请求,就需要设置请求体,以及请求方式.
[request setHTTPBody:[[self appendStringByParDic] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
}
//3.创建连接对象(Delegate方式)
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
} //结束请求数据
- (void)stopDownloadData {
[self.connection cancel]; //中断连接
} #pragma mark - handle
/**
* 获取网址字符串对象
*/
- (NSString *)urlString {
//保存服务器地址
NSString *newLinkName = nil;
//判断请求方式
switch (self.requestMethod) {
case RequsetMethodGET: //GET请求,需要拼接上参数
newLinkName = [self urlStringByGET];
break;
case RequsetmethodPOST: //POST请求,无需拼接
newLinkName = self.linkName;
break;
default:
break;
}
return newLinkName;
} //GET请求获取请求的网址字符串对象
- (NSString *)urlStringByGET {
//判断参数字段中是否有参数
if (!self.parmDic) {
//如果没有参数,则不需要拼接,直接返回服务器地址即可.
return self.linkName;
}
//如果有参数,则需要拼接
return [[self.linkName stringByAppendingFormat:@"?%@", [self appendStringByParDic]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
} //将字典中的所有参数拼接成字符串
- (NSString *)appendStringByParDic {
//1.创建可变数组,存储字典中的key:Value元素
NSMutableArray *tempArr = [NSMutableArray arrayWithCapacity:];
//2.遍历字典,将key:value两部分合成一个字符串对象
for (NSString *key in self.parmDic) {
NSString *newStr = [NSString stringWithFormat:@"%@=%@", key, self.parmDic[key]];
[tempArr addObject:newStr];
}
//3.将数组中的所有元素拼接成一个字符串,中间通过&间隔.
return [tempArr componentsJoinedByString:@"&"];
} #pragma mark - NSURLConnectionDataDelegate //收到服务器响应
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//开辟空间
self.mData = [NSMutableData data];
} //收到服务器数据
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
//拼接数据
[self.mData appendData:data];
}
//数据传输完毕
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//通知代理执行协议中请求数据成功的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestSuccessWithData:)]) {
[self.delegate networkEngine:self requestSuccessWithData:self.mData];
} }
//连接失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
//通知代理执行协议中请求数据失败的方法
if ([self.delegate respondsToSelector:@selector(networkEngine:requestFailWithData:)]) {
[self.delegate networkEngine:self requestFailWithData:error];
}
} - (void)dealloc {
[_linkName release];
[_parmDic release];
[_connection release];
[_mData release];
[super dealloc];
} @end
接下来呢?我们需要回到视图控制器中,在这里,我们会给NetworkEngine指定服务器地址和参数,让它去真正的请求数据,我们在视图控制器中接收数据并且进行解析,将解析的数据封装成一个一个Model对象存储起来,供tableView显示.
所以首先我们还是先建立Model对象吧,非常简单,我们需要让Cell显示什么数据,就在这个Model中定义什么属性,当然,也有一些东西需要注意.
1.在Models工程文件夹中建立News类
.h文件如下:
// News.h
// NewsDemo #import <Foundation/Foundation.h>
/*
{
"template": "manual",
"hasCover": false,
"hasHead": 1,
"skipID": "54GK0096|65860",
"replyCount": 2785,
"alias": "Entertainment",
"hasImg": 1,
"digest": "",
"hasIcon": true,
"skipType": "photoset",
"cid": "C1348648351901",
"docid": "9IG74V5H00963VRO_AQ01DJ36zongssupdateDoc",
"title": "[广角镜]何炅的“老师”生涯全回顾",
"hasAD": 1,
"order": 1,
"imgextra": [
{
"imgsrc": "http://img2.cache.netease.com/3g/2015/5/19/201505191431056e75c.jpg"
},
{
"imgsrc": "http://img4.cache.netease.com/3g/2015/5/19/201505191431082bd04.jpg"
}
],
"priority": 200,
"lmodify": "2015-05-19 14:16:05",
"ename": "yule",
"tname": "娱乐",
"imgsrc": "http://img6.cache.netease.com/ent/2015/5/19/2015051914225773b5e.jpg",
"photosetID": "54GK0096|65860",
"ptime": "2015-05-19 14:16:05"
},
*/ @interface News : NSObject
@property (nonatomic, copy) NSString *title; //标题
@property (nonatomic, copy) NSString *digest; //简介
@property (nonatomic, copy) NSString *imgsrc; //图片链接
@property (nonatomic, retain) NSArray *imgextra; //另外两个图片的链接,可能有,可能没有
@property (nonatomic, assign) NSInteger flag; //用来标识当前数据需要那一套Cell来展示,如果为0,对应NewsCell,为1,对应imageCell. - (instancetype)initWithDictionary:(NSDictionary *)dic; @end
上面注释中的内容是我们从网易服务器获取到的网易新闻的新闻源数据,我们做的Demo就是基于此,由于这里我们只需要显示标题,简介,和图片,别的暂时不需要.
所以我们定义属性:标题, 简介, 左侧的图片(第一套NewsCell显示), 多图显示时候的图片(第二套ImageCell显示), 以及用来标示当前数据需要哪一套Cell显示的flag.
因为News中存储的是一条新闻的信息,所以我们还需要添加一个初始化方法,这样在视图控制器中,才能将解析出来的一个新闻数据通过初始化赋值给一个News对象.供Cell来使用,取出需要的数据.
.m文件如下:
- (instancetype)initWithDictionary:(NSDictionary *)dic {
self = [super init];
if (self) {
//KVC赋值
[self setValuesForKeysWithDictionary:dic];
//需要判断属性imgextra有没有赋值,如果赋值了,说明此时含有三张图片链接,则使用ImageCell展示.否则使用NewsCell
if (self.imgextra) {
self.flag = ; //使用ImageCell
} else {
self.flag = ; //使用NewsCell
}
}
return self;
}
//为没有对应的属性的Key实现此方法,防止程序crash
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { } + (id)newsWithDictionary:(NSDictionary *)dic {
return[[[self alloc] initWithDictionary:dic] autorelease];
}
使用KVC赋值,安全起见,一定要调用一下方法,为没有对应属性的key实现此方法.
News创建完毕,回到控制器中.接上第一步,给NetworkEngine指定服务器地址和参数,让它去真正的请求数据,我们在视图控制器中接收数据并且进行解析,将解析的数据封装成一个一个Model对象存储起来,供tableView显示.
部分实现代码如下.
@interface NewsListController ()<NetworkEngineDelegate>
@property (nonatomic, retain) NSMutableArray *dataSource; //存储所有的News对象 @end @implementation NewsListController //懒加载数组
- (NSMutableArray *)dataSource {
if (!_dataSource) {
self.dataSource = [NSMutableArray arrayWithCapacity:];
}
return [[_dataSource retain] autorelease];
} //请求新闻数据
- (void)requsetData {
NetworkEngine *engine = [[NetworkEngine alloc] init];
//设置请求网址
engine.linkName = [NSString stringWithFormat:kNewsAPI, ];//0 - 20条数据
//设置请求方式
engine.requestMethod = RequsetMethodGET; //GET请求方式
//设置代理
engine.delegate = self;
//开始网络请求
[engine startDownloadData];
[engine release];
} #pragma mark - NetworkEngineDelegate
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data {
//解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//根据Key获取存储新闻的数组
NSArray *results = dic[@"T1348648517839"];
//将数组中的每个小字典封装成NewsModel对象
for (NSDictionary *dic in results) {
News *news = [[News alloc] initWithDictionary:dic];
//添加到数组中
[self.dataSource addObject:news];
}
//让tableView刷新数据
[self.tableView reloadData]; }
- (void)networkEngine:(NetworkEngine *)networkEngine requestFailWithData:(NSError *)error { } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return ;
} - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSUInteger count = self.dataSource.count;
return count ? count + : ; //因为有最后一个"上拉加载更多的Label",如果没有请求数据成功,那就不展示Cell.
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //如果是最后一行,则显示加载Cell
if (indexPath.row == self.dataSource.count) {
LoadingCell *cell = [tableView dequeueReusableCellWithIdentifier:kLoadingCellReuseIdentifier forIndexPath:indexPath];
return cell;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
//为0使用NewsCell
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:kNewsCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} else {
//为1,使用imageCell
ImageCell *cell = [tableView dequeueReusableCellWithIdentifier:kImageCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} } //动态设置每一行Cell的高度
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//对于NewsCell,高度为80,ImageCell为100, loadingCell为30 //如果索引和元素个数相同,说明是最后一行
if (indexPath.row == self.dataSource.count) {
return ;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
return ;
} else {
return ;
}
}
分析:
第一步,肯定是请求新闻数据.给NetworkEngine设定服务器地址,参数,请求方式等.这里写成一个方法,不要忘了在viewDidLoad中调用.
既然请求了数据,我们就要接收,怎么接收呢?我们已经在NetworkEngine中定义了代理属性,就是用来传数据的,所以这里设置控制器为代理对象,并且实现协议中的方法,将接收到的数据进行解析.
//请求新闻数据
- (void)requsetData {
NetworkEngine *engine = [[NetworkEngine alloc] init];
//设置请求网址
engine.linkName = [NSString stringWithFormat:kNewsAPI, ];//0 - 20条数据
//设置请求方式
engine.requestMethod = RequsetMethodGET; //GET请求方式
//设置代理
engine.delegate = self;
//开始网络请求
[engine startDownloadData];
[engine release];
}
在协议方法中进行数据解析:(因为协议方法中传过来的是接收完毕后的数据,所以这里进行解析.)(已知为JSON数据,并且最外层为一个字典).
我们需要先定义一个属性dataSource,用来存储解析之后并封装成News对象的数据.
#pragma mark - NetworkEngineDelegate
- (void)networkEngine:(NetworkEngine *)networkEngine requestSuccessWithData:(NSData *)data {
//解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
//根据Key获取存储新闻的数组
NSArray *results = dic[@"T1348648517839"];
//将数组中的每个小字典封装成NewsModel对象
for (NSDictionary *dic in results) {
News *news = [[News alloc] initWithDictionary:dic];
//添加到数组中
[self.dataSource addObject:news];
}
//让tableView刷新数据
[self.tableView reloadData]; }
到这里,数据解析也已经完成,存储在dataSource中,剩下的工作就是让每一套Cell显示它应该显示的数据.
这里可以很方便的利用我们之前在News中定义的flag属性来进行判断.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSUInteger count = self.dataSource.count;
return count ? count + : ; //因为有最后一个"上拉加载更多的Label",如果没有请求数据成功,那就不展示Cell.
} - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //如果是最后一行,则显示加载Cell
if (indexPath.row == self.dataSource.count) {
LoadingCell *cell = [tableView dequeueReusableCellWithIdentifier:kLoadingCellReuseIdentifier forIndexPath:indexPath];
return cell;
} News *news = self.dataSource[indexPath.row];
if (news.flag == ) {
//为0使用NewsCell
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:kNewsCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
} else {
//为1,使用imageCell
ImageCell *cell = [tableView dequeueReusableCellWithIdentifier:kImageCellReuseIdentifier forIndexPath:indexPath];
[cell configureCellWithNews:news];
return cell;
}
}
最后,需要说明的时是,在自定义Cell的时候,我们使用了第三方的显示图片工具EGOImageView.它为我们提供了方便的显示图片方法,直接将解析出来的NSData数据中存储的图片的NSURL显示成图片.
实例:
.h文件 #import <UIKit/UIKit.h>
#import "EGOImageView.h"
#import "News.h" @interface ImageCell : UITableViewCell
@property (nonatomic, retain) UILabel *titleLabel; //标题
@property (nonatomic, retain) EGOImageView *leftImageView;
@property (nonatomic, retain) EGOImageView *centerImageView;
@property (nonatomic, retain) EGOImageView *rightImageView; //赋值
- (void)configureCellWithNews:(News *)news;
@end .m文件
//赋值
- (void)configureCellWithNews:(News *)news {
//左边的图片
self.leftImageView.imageURL = [NSURL URLWithString:news.imgsrc];
//中间的图片
self.centerImageView.imageURL = [NSURL URLWithString:news.imgextra[][@"imgsrc"]];
//右边的图片
self.rightImageView.imageURL = [NSURL URLWithString:news.imgextra[][@"imgsrc"]];
//Title标题
self.titleLabel.text = news.title;
}
OVER.
自己还是不够熟练,不能够非常熟练的使用MVC,界面之间的数据传输是弱项,以后还要多加练习啊!
iOS学习之网易新闻简易Demo的更多相关文章
- iOS界面-仿网易新闻左侧抽屉式交互 续(添加新闻内容页和评论页手势)
本文转载至 http://blog.csdn.net/totogo2010/article/details/8637430 1.介绍 有的博友看了上篇博文iOS界面-仿网易新闻左侧抽屉 ...
- IOS之分析网易新闻存储数据(CoreData的使用,增删改查)
用过网易新闻客户端的朋友们都知道,获取新闻列表时有的时候他会请求网络有时候不会,查看某条新闻的时候再返回会标注已经查看的效果,接下来分析一下是如何实现的. 首先: 1.网易新闻用CoreData存储了 ...
- IOS开发--仿制网易新闻
学习来源:袁峥老师的<快速集成App中顶部标题滚动条> 此次博文写的是按需求分析写代码,思路条理性杠杠的,可以提高的编码实现速度哦. 效果: 根据这个网易新闻的界面,需求分析: ...
- 【转】 iOS开发 剖析网易新闻标签栏视图切换(addChildViewController属性介绍)
原文:http://blog.csdn.net/hmt20130412/article/details/34523235 本来只是打算介绍一下addChildViewController这个方法的,正 ...
- iOS开发 剖析网易新闻标签栏视图切换(addChildViewController属性介绍)
本文转载至 http://www.tuicool.com/articles/3ymMzub CSDN博客原文 http://blog.csdn.net/hmt20130412/article/det ...
- iOS 网易新闻用到的框架
网易新闻iOS版在开发过程中曾经使用过的第三方开源类库.组件 1.AFNetworking AFNetworking 采用 NSURLConnection + NSOperation, 主要方便与服务 ...
- iOS仿网易新闻栏目拖动重排添加删除效果
仿网易新闻栏目选择页面的基本效果,今天抽了点时间教大家如何实现UICollectionView拖动的效果! 其实实现起来并不复杂,这里只是基本的功能,没有实现细节上的修改,连UI都是丑丑的样子,随手画 ...
- 网易新闻iOS版使用的18个开源组件
转载来自:http://www.jianshu.com/p/8952944f7566 原文最后编辑时间:2015.05.19 网易新闻iOS版在开发过程中曾经使用过的第三方开源类库.组件 1.AFN ...
- Android(java)学习笔记206:利用开源SmartImageView优化网易新闻RSS客户端
1.我们自己编写的SmartImageView会有很多漏洞,但是我们幸运的可以在网上利用开源项目的,开源项目中有很多成熟的代码,比如SmartImageView都编写的很成熟的 国内我们经常用到htt ...
随机推荐
- Android 之形状Drawable
形状Drawable资源允许使用 <shape>标记指定基本形状的尺寸.背景.轮廓线,从而定义这些基本形状. 每个形状都包含一个类型(通过shape属性指定).定义该形状尺寸的属性,以及指 ...
- LINQ 学习笔记(1)
学习资源参考 : http://www.cnblogs.com/lifepoem/archive/2011/12/16/2288017.html 常用方法是 Where, OrderBy, Selec ...
- FE: CSS固定图片显示大小及GitHub Pages在线演示
CSS固定图片显示大小 分析 假设图片区域的大小固定为250×300px,那么我们可以写出如下的样式 .picture-area { width: 250px; height: 300px; marg ...
- windows 查看文件被哪个进程占用
经常当我们删除文件时,有时会提示[操作无法完成,因为文件已在另一个程序中打开,请关闭该文件并重试],到底是哪些程序呢? 有时候一个一个找真不是办法,已经被这个问题折磨很久了,今天下决心要把它解决,找到 ...
- 【8】JAVA---地址App小软件(AddrDaoFile .class)(数据层)
实现数据进行文件的存储和读写. 本软件也就到此结束了. 没多少可以讲的. 因为这个小软件也就8个类,主要学习的也就是一个分层思想的简单应用. package cn.hncu.addr.dao; imp ...
- android后台截屏实现(1)--源码编译
前段时间接到任务要实现后台截图并上传的功能,在网上查了好久,发现遇到这类问题的人还不少.经过一番对比后发现还是修改并编译源码中的screencap类然后通过JNI来调用这种方法比较可靠,而其他的在ja ...
- 公告:CSDN博客频道新功能正式上线!
各位尊敬的CSDN用户: 你们好! 为了更好的服务于用户,CSDN博客最新推出如下功能: 1.取消开通博客3天才能发布博文的限制,博客开通之后即可发表博文 2.博客文章增加自定义摘要功能 在发表 ...
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES错误解决方法
在安装APK文件时出现类似INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES的提示,同时类似的提示如下: Android Launch! adb is run ...
- 【Android】Activity的菜单机制和方法解析
Activity有一套机制来实现对菜单的管理,方法如下: 1. 初始化菜单 public boolean onCreateOptionsMenu(Menu menu) 此方法用于初始化菜单,其中men ...
- (1)QlikView概要
本文的内容,以学习的两个合伙人: I.什么是Qlikview II. QlikView 的优点和缺点 1.1什么是QlikView 1.1什么是QlikView QlikView是一个工具,一个商业智 ...