基于iOS快递查询App的实现

功能介绍


  • 实现提供快递单号快递公司查询快递轨迹
  • 实现二维码条形码扫描查询
  • 实现对个人查询记录保存
  • 实现市场常见的国内与国际快递电话的查询与拨打
  • 运行截图


    运行截图

前期准备


  • 快递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 = []
#快递名称都位于标签

中 #去掉网页中位于快递名称后的“电话”两个字并保存在express中 for li in soup.find_all('h4'): data = str(li.get_text()).replace("电话","") express.append(data) #快递电话位于标签中 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,因此将其保留。在控制台中执行.py程序时,只需要Python3 xxx.py即可。

程序实现


代码放在Github上了,欢迎folk,欢迎star
程序代码

  • 程序示意图


    基于iOS快递查询App的实现_第1张图片
    iOS快递模块.png

基于iOS快递查询App的实现_第2张图片
程序界面

  • 查询模块:
    1. 用户输入:快递单号(Required),快递公司(Required),快递备注(Optional,用户在快递历史模块提供备注查看的作用);
    2. 获取用户输入的快递单号和快递公司信息,将请求的参数按照快递鸟官方要求的格式处理(在这里会用到MD5加密以及Base64编码);
    3. 借助第三方库:AFNetworking ,将第二步中处理好的数据POST给快递鸟,快递鸟返回Json数据;
    4. 处理返回的Json数据并且显示物流信息;
    5. 用户点击“保存”按钮,将本单的物流信息保存到plist文件当中实现数据的持久化存储。
    6. 用户还可以使用二维码或条形码扫描功能,实现自动识别快递单号。
      利用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 *)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];
}

  • 快递历史模块:
    1. 获取plist中的物流信息;
    2. 显示到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];

  • 快递电话模块
    1. 新建一个快递模型Class:ExpressPhoneNum.其中有两个成员变量,快递名称和快递电话;
    2. 将获取到的快递信息存储为ExpressPhoneNum类型
    3. 将数据显示到tableview
    4. 点击某行实现拨打电话的功能
//点击行打电话
-(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

你可能感兴趣的:(基于iOS快递查询App的实现)