基于iOS快递查询App的实现

功能介绍


  • 实现提供<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

程序界面

  • 查询模块:
    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<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];
}

  • 快递历史模块:
    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,有待优化

参考资料


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容