功能介绍
- 实现提供<b>快递单号</b>和<b>快递公司</b>查询快递轨迹
- 实现<b>二维码</b>和<b>条形码</b>扫描查询
- 实现对个人查询记录保存
- 实现市场常见的国内与国际快递电话的查询与拨打
-
运行截图
运行截图
前期准备
- 快递api的选择:
目前提供免费的快递api查询的主要有快递鸟,快递100等。在本文中,我选择了快递鸟作为我的快递api查询接口。主要原因是:快递鸟对常见的快递(顺丰,圆通,中通,韵达等)都提供免费查询,而快递100则需要通过其他方式,自助定制性不强。
具体注册步骤可以参见官网教程。 - 快递电话名称信息的获取:
在App的第三个View中,实现了对常见快递客服电话的查询拨打,这里的信息通过网络爬虫获取。
快递电话地址 - 爬虫用到的工具:urllib2+BeautifulSoup
- 本文我这里为了图省事,直接将上述网页中的源代码保存到代码中存储为html_doc进行处理。
#-*- coding:utf-8 -*-
from bs4 import BeautifulSoup
html_doc="""这是上述网页的源代码"""
soup = BeautifulSoup(html_doc,"html.parser")
#用来存储快递名称的数组
express = []
#用来存储快递电话号码的数组
phoneNum = []
#快递名称都位于标签<h4>中
#去掉网页中位于快递名称后的“电话”两个字并保存在express中
for li in soup.find_all('h4'):
data = str(li.get_text()).replace("电话","")
express.append(data)
#快递电话位于标签<b>中
for li in soup.find_all('b'):
data = li.get_text()
phoneNum.append(data)
expressLen = len(express)
#输出到控制台上
for i in range(expressLen):
print('express'+str(i)+',',end="")
- 处理中遇到的问题:
- 开始使用Python2.7,中文处理总出问题,编码解码网上也查了很多资料,最后还是放弃专用了3.4,一试就成功;
- Python2.7 和 Python3.4 可以在电脑上共存,由于mac os中某些系统程序会用到2.7,因此将其保留。在控制台中执行<b>.py</b>程序时,只需要<b>Python3 xxx.py</b>即可。
程序实现
代码放在Github上了,欢迎folk,欢迎star
程序代码
-
程序示意图
iOS快递模块.png
程序界面
- 查询模块:
- 用户输入:快递单号(Required),快递公司(Required),快递备注(Optional,用户在快递历史模块提供备注查看的作用);
- 获取用户输入的快递单号和快递公司信息,将请求的参数按照快递鸟官方要求的格式处理(在这里会用到MD5加密以及Base64编码);
- 借助第三方库:AFNetworking ,将第二步中处理好的数据POST给快递鸟,快递鸟返回Json数据;
- 处理返回的Json数据并且显示物流信息;
- 用户点击“保存”按钮,将本单的物流信息保存到plist文件当中实现数据的持久化存储。
- 用户还可以使用二维码或条形码扫描功能,实现自动识别快递单号。
利用AFNetworking POST网络参数
//网络请求的部分代码
-(IBAction)expressSearch:(id)sender {
//1.编写上传参数
//获取快递公司对应的编号
NSString* shipperCode = shipperCodeUser;
//获取快递单号
NSString* logisticCode = self.expressNum.text;
//根据快递鸟api请求的要求格式进行数据的处理
//其中eBusinessID,appKey是快递鸟api提供
//reqURL:请求的网址,同样是由快递鸟api提供
NSString* requestData = [NSString stringWithFormat:@"{\"OrderCode\":\"\",\"ShipperCode\":\"%@\",\"LogisticCode\":\"%@\"}",shipperCode,logisticCode];
NSMutableDictionary* params = [[NSMutableDictionary alloc]init];
NSString* dataSignTmp = [[NSString alloc]initWithFormat:@"%@%@",requestData,appKey];
//这里使用到了MD5加密程序,以及Base64编码
NSString* dataSign = [[MD5 md5String:dataSignTmp] base64String];
[params setObject:[requestData stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"RequestData"];
[params setObject:eBusinessID forKey:@"EBusinessID"];
[params setObject:@"1002" forKey:@"RequestType"];
[params setObject:[dataSign stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:@"DataSign"];
[params setObject:@"2" forKey:@"DataType"];
//2.上传参数并获得返回值
AFHTTPSessionManager* manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:reqURL parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功:%@",responseObject);
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:0 error:nil];
//3. 获得网络数据赋值给ExpressInfo对象
NSMutableArray* expressTraces = [[NSMutableArray alloc]init];
for (NSDictionary* traces in [json objectForKey:@"Traces"]) {
[expressTraces insertObject:traces atIndex:0];
}
NSString* shipperCode = [json objectForKey:@"ShipperCode"];
NSString* logisticCode = [json objectForKey:@"LogisticCode"];
NSString* expressForUser = self.expressForUser.text;
ExpressInfo* express = [[ExpressInfo alloc]initWitfShipperCode:shipperCode andlogisticCode:logisticCode andexpressForUser:expressForUser andexpressTraces:expressTraces];
//4. 传递数据给ExpresstracesViewController
//获取的物流数据在ExpressTracesViewController中显示
ExpressTracesViewController* expressTracesVC = [[ExpressTracesViewController alloc]init];
expressTracesVC.express = express;
self.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:expressTracesVC animated:YES];
self.hidesBottomBarWhenPushed = NO;
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败:%@",error.description);
}];
}
向Plist中读取并写入数据
//保存数据到plist中的代码
-(void)saveExpressTraces{
//1. 获得文件路径
NSString* path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString* fileName = [path stringByAppendingPathComponent:@"123.plist"];
//2. 获得本单快递信息
NSArray* array = [NSArray arrayWithObjects:shipperCode,logisticCode,expressForUser,expressTraces, nil];
//3. 获取已经保存的快递信息
NSMutableArray* arrayCollection = [[NSMutableArray alloc]init];
NSArray *result = [ NSArray arrayWithContentsOfFile:fileName];
for (NSArray* obj in result) {
[arrayCollection addObject:obj];
}
//4. 将本单快递信息追加到已有的信息中
[arrayCollection addObject:array];
//5. 存储
[arrayCollection writeToFile:fileName atomically:YES];
//6. 提示框
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"保存成功" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
[alert show];
}
选择iOS系统相册图片
//获取相册照片
// 1.判断相册是否可以打开
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) return;
// 2. 创建图片选择控制器
UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// 3.设置代理
ipc.delegate = self;
// 4.modal出这个控制器
[self presentViewController:ipc animated:YES completion:nil];
//实现相关协议
#pragma mark -------- UIImagePickerControllerDelegate---------
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
// 1.取出选中的图片
UIImage *pickImage = info[UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(pickImage);
CIImage *ciImage = [CIImage imageWithData:imageData];
// 2.从选中的图片中读取二维码数据
// 2.1创建一个探测器
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
// 2.2利用探测器探测数据
NSArray *feature = [detector featuresInImage:ciImage];
// 2.3取出探测到的数据
for (CIQRCodeFeature *result in feature) {
NSLog(@"%@",result.messageString);
NSString *urlStr = result.messageString;
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
}
// 注意: 如果实现了该方法, 当选中一张图片时系统就不会自动关闭相册控制器
[picker dismissViewControllerAnimated:YES completion:nil];
}
利用摄像头捕捉二维码或者条形码
#pragma mark --------AVCaptureMetadataOutputObjectsDelegate ---------
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
if (object == nil) return;
// 只要扫描到结果就会调用
self.customLabel.text = object.stringValue;
NSDictionary* userInfo = [NSDictionary dictionaryWithObject:object.stringValue forKey:@"userInfo"];
[self clearLayers];
[self dismissViewControllerAnimated:YES completion:^{
[[NSNotificationCenter defaultCenter]postNotificationName:@"do" object:self userInfo:userInfo];
}];
/*
将secendView dismissViewControllerAnimated掉,然后自动注册一个名为do的通知
注册了这个名为的通知,你就可以在任何.m文件里面通过以下代码调用到了:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(handleColorChange:)
name:@"do"
object:nil];
上面的代码的意思就是,先找到已经注册过的名为do的通知,然后再自动调用handleColorChange去处理
*/
// 2.对扫描到的二维码进行描边
AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[self.previewLayer transformedMetadataObjectForMetadataObject:object];
[self drawLine:obj];
}
- 快递历史模块:
- 获取plist中的物流信息;
- 显示到tableview中。
//获取plist文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
//得到123.plist文件
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
//获取123.plist文件中的返回值
NSArray *result = [ NSArray arrayWithContentsOfFile:fileName];
//将返回值复制给(NSMutableArray* )expressHistory
expressHistory = [NSMutableArray arrayWithArray:result];
- 快递电话模块
- 新建一个快递模型Class:ExpressPhoneNum.其中有两个成员变量,快递名称和快递电话;
- 将获取到的快递信息存储为ExpressPhoneNum类型
- 将数据显示到tableview
- 点击某行实现拨打电话的功能
//点击行打电话
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//获取点击的Section
NSArray* expressXX = self.expressArray[indexPath.section];
//获取点击的快递信息
ExpressPhoneNum* expressXXX = expressXX[indexPath.row];
NSString* expressPhoneNumber = expressXXX.expressNum;
NSMutableString* str = [[NSMutableString alloc]initWithFormat:@"tel:%@",expressPhoneNumber];
//实现拨号功能
UIWebView* callWebView = [[UIWebView alloc]init];
[callWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:str]]];
[self.view addSubview:callWebView];
NSLog(@"打电话啊!");
}
下一步的工作
- 界面美化:程序当中所有的控件都是系统默认的,未做任何修改;
- 程序整体功能执行没问题,但存在个别bug,有待优化
参考资料
- Base64编码:ekscrypto/Base64
- MD5 加密:MD5加密
- 二维码扫描:JustKeepRunning/LXDScanQRCode
网友评论
Linker command failed with exit code 1的错误 怎么解决
在stackoverflow上搜索的答案并不适用
diff: /../Podfile.lock: No such file or directory
diff: /Manifest.lock: No such file or directory
error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
但是我发现您的包内并没有包含pod文件。不知道是怎么回事呢?
platform:ios,'8.0'
pod 'AFNetworking', '~> 3.1.0'