zoukankan      html  css  js  c++  java
  • Objective-C MapKit的使用-LBS简单的租车主界面demo

    效果

    效果.gif

    分析

    • 三个view:地图view、车辆信息view、车辆类型选择view
    • 地图view:大头针的摆放,根据不同的种类显示大头针
    • 车辆信息view:根据当前点击的大头针显示对应的车辆信息
    • 车辆类型选择view:选择车辆类型
    • 交互分析
    • 选择车辆类型,地图上出现不同的大头针
    • 车辆信息view可滑动,滑动完成后地图定位到当前车辆的大头针上
    • view的搭建
    • 车辆选择view:自定义slider 分段滑竿(上一篇文章提到过
    • 车辆信息View:使用uicollectionView的流水布局,做出分页效果
    • mapView:自定义大头针,根据类型选择不同的大头针图片
    • 参数的传递
    • 模拟数据通过编写1.plist完成
    • 读取plist数据,通过选择车辆类型,将筛选出来的数据使用模型数组存放,通过set方法传递给mapView和车辆信息view,并刷新界面
    • 选择车辆信息view 通过代理将当前显示的车辆信息页传递给mapview并定位
    • mapview有两个委托方法,点击空白处点击大头针两个方法,用来设置车辆选择view和车辆信息view的显隐

    代码

    数据模型

    • 数据模型及KVC使用
    #import <Foundation/Foundation.h>
    
    //车类型
    typedef NS_ENUM(NSInteger, CarType) {
        CarTypeNone = -1,        //默认大头针
        CarTypeDaily,           //日租
        CarTypeHourly,          //时租
        CarTypeLong,            //长租
        CarTypeTestDrive,       //试驾
    };
    @interface CarModel : NSObject
    @property(nonatomic,strong)NSDictionary *location;      //经纬度
    @property(nonatomic,assign)NSString *carType;             //车类型
    @property(nonatomic,copy)NSString *carName;             //车名称
    @property(nonatomic,copy)NSString *price;               //价格
    @property(nonatomic,copy)NSString *distance;            //与用户的距离
    @property(nonatomic,copy)NSString *locationName;        //当前位置名
    @property(nonatomic,copy)NSString *imageName;           //车图片
    @property(nonatomic,copy)NSString *order;               //订单
    @property(nonatomic,copy)NSString *banDay;              //限行
    - (instancetype)initWithCarModelDict:(NSDictionary*)dict;
    + (instancetype)carModelWithDict:(NSDictionary*)ditc;
    @end
    

    自定义collectionview

    使用xib的约束直接布局cell,cell外部公开carModel,用于赋值

    item布局

    • 给自定义的collectionview写一个委托,用来告诉controller当前选择cellitem
    #import <UIKit/UIKit.h>
    #import "CarModel.h"
    @protocol CarInfoCollectionViewDelegate <NSObject>
    - (void)selectItemArray:(NSArray*)array WithIndex:(NSInteger)index;
    @end
    
    @interface CarInfoCollectionView : UICollectionView
    @property (nonatomic,strong)NSMutableArray<CarModel*> *carModelArray;
    @property (nonatomic,strong)id<CarInfoCollectionViewDelegate> delegate2;
    @end
    
    • 界面呈现翻页效果 左右两边留上一页和下一页的边缘,需要计算停下的位置,使用UICollectionViewDelegate代理方法
    //停下的位置
    - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
    {
        UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout*)self.collectionViewLayout;
        //实际滑动的距离
        float pageWidth = layout.itemSize.width + layout.minimumLineSpacing;
        //当前位置
        float currentOffset = scrollView.contentOffset.x;
        //目标位置
        float targetOffset = targetContentOffset->x;
        float newTargetOffset = 0;
        int count = 0;
        if (targetOffset > currentOffset) {
            //向上取整
            count = ceilf(currentOffset / pageWidth);
            newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
        }else {
            //向下取整
            count = floorf(currentOffset / pageWidth);
            newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;
    
        }
        //处理边界
        if (newTargetOffset < 0)
            newTargetOffset = 0;
        else if (newTargetOffset > scrollView.contentSize.width)
            newTargetOffset = scrollView.contentSize.width;
        //设置目标位置指针
        targetContentOffset->x = currentOffset; 
        //跳转新位置
        [scrollView setContentOffset:CGPointMake(newTargetOffset, 0) animated:YES];
    }
    
    • 当界面滚动完成时,通过代理通知controller当前的cellitem
    - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
        UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout*)self.collectionViewLayout;
        //实际滑动的距离
        int pageWidth = layout.itemSize.width + layout.minimumLineSpacing;
        //当前位置
        int currentOffset = scrollView.contentOffset.x;
        int count = currentOffset / pageWidth;
    
        NSLog(@"滚完了 %d",count );
        if ([self.delegate2 respondsToSelector:@selector(selectItemArray:WithIndex:)]) {
            [self.delegate2 selectItemArray:self.carModelArray WithIndex:count];
        }
    }
    

    mapView

    • 代理
    #import <UIKit/UIKit.h>
    #import <MapKit/MapKit.h>
    #import "CarModel.h"
    @protocol MapViewDelegate <NSObject>
    //点击地图没有点到大头针
    - (void)didSelectMapWithoutAnnotation;
    //点到大头针
    - (void)didSelectMapAnnotationViewWithCarArray:(NSMutableArray<CarModel *>*)carArray WithIndex:(NSInteger)index;
    @end
    
    @interface MapView : UIView
    @property(nonatomic,strong)id<MapViewDelegate> delegate;
    @property (nonatomic,strong)MKMapView *map;
    //大头针数组
    @property (nonatomic,strong)NSMutableArray *annotationArray;
    //car数据模型数组
    @property (nonatomic,strong)NSMutableArray<CarModel *> *carModelArray;
    @end
    
    • 使用到的全局变量
    @interface MapView()<CLLocationManagerDelegate,MKMapViewDelegate>
    @property (nonatomic,strong)CLLocationManager *locationManager;
    @property (nonatomic,strong)UIButton *currentLocationBtn;
    @property (nonatomic,strong)UIButton *zoomInBtn; //放大
    @property (nonatomic,strong)UIButton *zoomOutBtn;//缩小
    @property (nonatomic,strong)MyAnnotation *userLocationAnnotation;
    @property (nonatomic,assign)int scale;
    @end
    
    • 初始化
    //初始化
    - (instancetype)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            
            self.carModelArray = [NSMutableArray array];
            [self loadingMapInfo];
            [self addSubview:self.map];
            [self addSubview:self.currentLocationBtn];
            [self addSubview:self.zoomInBtn];
            [self addSubview:self.zoomOutBtn];
        }
        return self;
    }
    
    • 全局变量使用懒加载
      需要提到的是当前位置的大头针的位置需要进行火星转码
    //当前位置大头针
    - (MyAnnotation *)userLocationAnnotation {
        if (!_userLocationAnnotation) {
            _userLocationAnnotation = [[MyAnnotation alloc] init];
            _userLocationAnnotation.type = CarTypeNone;
            //转火星坐标
            CLLocationCoordinate2D currentLocation = [WGS84TOGCJ02 transformFromWGSToGCJ:self.locationManager.location.coordinate];
            _userLocationAnnotation.coordinate = currentLocation;
            _userLocationAnnotation.title = @"我的位置";
        }
        return _userLocationAnnotation;
    }
    
    • 定位当前位置和放大缩小按钮的实现
    //定位
    - (UIButton *)currentLocationBtn {
        if (!_currentLocationBtn) {
            _currentLocationBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.frame)*0.6, 50, 50)];
            [_currentLocationBtn setImage:[UIImage imageNamed:@"location_my.png"] forState:UIControlStateNormal];
            [_currentLocationBtn addTarget:self action:@selector(clickCurrentBtn) forControlEvents:UIControlEventTouchUpInside];
        }
        return _currentLocationBtn;
    }
    //点击定位
    - (void)clickCurrentBtn {
        [self.map deselectAnnotation:self.userLocationAnnotation animated:NO];
        [self.map selectAnnotation:self.userLocationAnnotation animated:YES];
    }
    //放大
    - (UIButton *)zoomInBtn {
        if (!_zoomInBtn) {
            _zoomInBtn = [[UIButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.frame)-60, CGRectGetHeight(self.frame)*0.55, 50, 50)];
            [_zoomInBtn setImage:[UIImage imageNamed:@"zoomin.png"] forState:UIControlStateNormal];
            [_zoomInBtn addTarget:self action:@selector(mapZoomIn) forControlEvents:UIControlEventTouchUpInside];
        }
        return _zoomInBtn;
    }
    //放大方法
    - (void)mapZoomIn {
        MKCoordinateRegion region = self.map.region;
        region.span.latitudeDelta = region.span.latitudeDelta * 0.5;
        region.span.longitudeDelta = region.span.longitudeDelta * 0.5;
        [self.map setRegion:region animated:YES];
    }
    
    //缩小
    - (UIButton *)zoomOutBtn {
        if (!_zoomOutBtn) {
            _zoomOutBtn = [[UIButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(self.frame)-60, CGRectGetMaxY(self.zoomInBtn.frame), 50, 50)];
            [_zoomOutBtn setImage:[UIImage imageNamed:@"zoomout.png"] forState:UIControlStateNormal];
            [_zoomOutBtn addTarget:self action:@selector(mapZoomOut) forControlEvents:UIControlEventTouchUpInside];
        }
        return _zoomOutBtn;
    }
    
    //缩小方法
    - (void)mapZoomOut {
        MKCoordinateRegion region = self.map.region;
        region.span.latitudeDelta = region.span.latitudeDelta * 2;
        region.span.longitudeDelta = region.span.longitudeDelta * 2;
        [self.map setRegion:region animated:YES];
    }
    
    • 授权使用定位功能 info.plist上添加Privacy - Location Always Usage Description 值随便填
    //获取授权
    - (void)getAuthorization {
        //获取授权状态
        CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
        if (status == kCLAuthorizationStatusNotDetermined) {
            [self.locationManager requestAlwaysAuthorization];
        } else if (status == kCLAuthorizationStatusAuthorizedAlways) {
            //不跟随用户
            self.map.userTrackingMode = MKUserTrackingModeNone;
        }
    }
    
    //授权后进入
    - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{ 
        //授权后开始定位
        [self.locationManager startUpdatingLocation];
        //加载地图信息
        [self loadData];
        [self loadingMapInfo];
    }
    
    • 公开变量设置set方法用于刷新地图大头针信息
    //set方法
    - (void)setCarModelArray:(NSMutableArray<CarModel *> *)carModelArray {
        
        for (MyAnnotation *an in self.map.annotations) {
            if (an.type != CarTypeNone) {
                [self.map removeAnnotation:an];
            }
        }
        [_carModelArray removeAllObjects];
        _carModelArray = carModelArray;
        
        //重新加载数据
        [self loadData];
    }
    
    //加载模拟数据
    - (void)loadData {
    
        [self.annotationArray removeAllObjects];
        //完善model数据
        for (CarModel *model in self.carModelArray) {
            CLGeocoder *coder = [[CLGeocoder alloc] init];
            //model中的位置
            CLLocation *location = [[CLLocation alloc] initWithLatitude:[model.location[@"lat"] doubleValue] longitude:[model.location[@"long"] doubleValue]];
            //反地理编码 获得 经纬度 对应的 地名 并计算与当前位置的距离
            [coder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
                CLPlacemark *mark = [placemarks firstObject];
                CLLocationCoordinate2D locatio = [WGS84TOGCJ02 transformFromWGSToGCJ:self.locationManager.location.coordinate];
                CLLocation *currentLocation =  [[CLLocation alloc] initWithLatitude:locatio.latitude longitude:locatio.longitude];
                CLLocationDistance dis = [location distanceFromLocation:currentLocation];
                
                model.locationName = mark.name;
                model.distance = [NSString stringWithFormat:@"%.2fkm",dis/1000];
            }];
        }
        
        int count = 0;
        //加载大头针
        for (CarModel *model in self.carModelArray) {
            
            MyAnnotation *annotation = [[MyAnnotation alloc] init];
            CLLocationCoordinate2D location = CLLocationCoordinate2DMake([model.location[@"lat"] doubleValue], [model.location[@"long"] doubleValue]);
            annotation.coordinate = location;
            annotation.index = count;
            annotation.type = [model.carType intValue];
            [self.map addAnnotation:annotation];
            [self.annotationArray addObject:annotation];
            count++;
        }
    }
    
    
    • mapview自身的代理方法,点击和取消大头针,实现回调跳转车辆信息view
    #pragma mark - MKMapViewDelegate
    //点击大头针
    - (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
        //重置汽车原来的颜色
        NSArray *array = [mapView annotations];
        for (MyAnnotation *an in array) {
            MKAnnotationView *v = [mapView viewForAnnotation:an];
            if ([v.reuseIdentifier isEqualToString:@"carViewID"]) {
                v.image = [self getCarImageWithTypeInAnnotation:an];
            }
        }
        //点击小车换颜色
        if ([view.reuseIdentifier isEqualToString:@"carViewID"]) {
            view.image = [UIImage imageNamed:@"pickcar.png"];
            //代理回调 通知界面 将 carInfoView 出现 carPickView消失
            if ([self.delegate respondsToSelector:@selector(didSelectMapAnnotationViewWithCarArray:WithIndex:)]) {
                [self.delegate didSelectMapAnnotationViewWithCarArray:self.carModelArray WithIndex:((MyAnnotation*)view.annotation).index];
            }
            //设置中心点和范围
            [self.map setRegion:MKCoordinateRegionMakeWithDistance(((MyAnnotation*)view.annotation).coordinate, 2000, 2000) animated:NO];
    
        }
        else {
            [self.map setRegion:MKCoordinateRegionMakeWithDistance(self.userLocationAnnotation.coordinate, 2000, 2000) animated:YES];
        }
    }
    
    //没有选中大头针
    - (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
        //重置汽车原来的颜色
        NSArray *array = [mapView annotations];
        for (MyAnnotation *an in array) {
            MKAnnotationView *v = [mapView viewForAnnotation:an];
            if ([v.reuseIdentifier isEqualToString:@"carViewID"]) {
                v.image = [self getCarImageWithTypeInAnnotation:an];
            }
        }
        if ([view.reuseIdentifier isEqualToString:@"carViewID"]) {
            //代理回调 通知界面 将 carInfoView 消失 carPickView出现 小车变为未选中
            if ([self.delegate respondsToSelector:@selector(didSelectMapWithoutAnnotation)]) {
                [self.delegate didSelectMapWithoutAnnotation];
            }
        }
    }
    
    • 自定义大头针 当前位置使用标注 其他位置使用自定义的大头针视图
    //当前位置大头针
    - (MKPinAnnotationView*)customLocalAnnotationView:(id<MKAnnotation>)annotation {
        static NSString *locationID = @"locationViewID";
        //从缓存池中获取大头针
        MKPinAnnotationView *pinView = ( MKPinAnnotationView *)[self.map dequeueReusableAnnotationViewWithIdentifier:locationID];
        if (pinView == nil) {
            pinView  = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:locationID];
        }
        pinView.annotation = annotation;
        // 取消气泡显示
        pinView.canShowCallout = YES;
        // 设置大头针是否有下落动画
        pinView.animatesDrop = YES;
        return pinView;
        
    }
    
    
    //自定义大头针
    - (MKAnnotationView*)customMKAnnotationView:(id<MKAnnotation>)annotation {
        //自定义大头针
        static NSString *carViewID = @"carViewID";
        //从缓存池中获取自定义大头针
        MKAnnotationView *annoView = [self.map dequeueReusableAnnotationViewWithIdentifier:carViewID];
        if (annoView == nil) {
            //缓存池中没有则创建
            annoView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:carViewID];
        }
        annoView.annotation = annotation;
        annoView.canShowCallout = NO;
        annoView.draggable = YES;
        annoView.image = [self getCarImageWithTypeInAnnotation:annotation];
        return annoView;
    }
    
    //根据大头针的类型返回图片
    - (UIImage *)getCarImageWithTypeInAnnotation:(MyAnnotation*)annotation {
        switch (annotation.type) {
            case CarTypeDaily:
                return  [UIImage imageNamed:@"dailycar.png"];
                break;
            case CarTypeHourly:
                return [UIImage imageNamed:@"hourlycar.png"];
                break;
            case CarTypeLong:
                return [UIImage imageNamed:@"longcar.png"];
                break;
            case CarTypeTestDrive:
                return [UIImage imageNamed:@"testcar.png"];
                break;
            default:
                break;
        }
        return nil;
    }
    

    viewController主界面

    • 将三个视图定义为全局 并使用懒加载
      collectionView使用流水布局,为显示翻页效果需要配合增加头尾空白
    - (CarInfoCollectionView *)collectionView {
        if (!_collectionView) {
            UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
            layout.itemSize = CGSizeMake(kScreenWidth * 0.85, kScreenHeight*0.2);
            layout.minimumLineSpacing = kScreenWidth * 0.05;
            
            CGFloat referenceWidth = (kScreenWidth - layout.itemSize.width - 2*layout.minimumLineSpacing)/2 + layout.minimumLineSpacing;
            
            layout.headerReferenceSize = CGSizeMake(referenceWidth, 0);
            layout.footerReferenceSize = CGSizeMake(referenceWidth, 0);
            layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
            _collectionView = [[CarInfoCollectionView alloc] initWithFrame:CGRectMake(0, kScreenHeight*0.8, self.view.frame.size.width, kScreenHeight*0.2) collectionViewLayout:layout];
            _collectionView.hidden = YES;
            _collectionView.delegate2 = self;
    
        }
        return _collectionView;
    }
    
    - (MapView *)mapView {
        if (!_mapView) {
            _mapView = [[MapView alloc] initWithFrame:self.view.bounds];
            _mapView.delegate = self;
        }
        return _mapView;
    }
    
    - (CustomSlider *)carPickView {
        if (!_carPickView) {
            _carPickView = [[CustomSlider alloc] initWithFrame:CGRectMake(0, kScreenHeight*0.8, kScreenWidth, kScreenHeight*0.2)];
            _carPickView.backgroundColor = [UIColor whiteColor];
            _carPickView.sliderBarHeight = 10;
            _carPickView.numberOfPart = 4;
            _carPickView.partColor = [UIColor grayColor];
            _carPickView.sliderColor = [UIColor grayColor];
            _carPickView.thumbImage = [UIImage imageNamed:@"yuanxing.png"];
            _carPickView.partNameOffset = CGPointMake(0, -30);
            _carPickView.thumbSize = CGSizeMake(50, 50);
            _carPickView.partSize = CGSizeMake(20, 20);
            _carPickView.partNameArray = @[@"日租",@"时租",@"长租",@"试驾"];
            [_carPickView addTarget:self action:@selector(valuechange:) forControlEvents:UIControlEventValueChanged];
        }
        return  _carPickView;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self.view addSubview:self.mapView];
        [self.view addSubview:self.collectionView];
        [self.view addSubview:self.carPickView];
        [self valuechange:self.carPickView];
    }
    
    
    • 从plist中加载数据
    - (NSMutableArray<CarModel*> *) selectCarWithType:(CarType)type {
        NSMutableArray *resultArray = [NSMutableArray array];
        //读取数据
        NSString *path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"plist"];
        NSArray *array = [NSArray arrayWithContentsOfFile:path];
    
        //存数据
        for (NSDictionary *dict in array) {
            CarModel *model = [CarModel carModelWithDict:dict];
            if ([model.carType intValue] == type) {
                [resultArray addObject:model];
            }
        }
        return resultArray;
    }
    
    • 实现各种委托方法
    - (void)valuechange:(CustomSlider*)sender {
        NSLog(@"%ld",(long)sender.value);
        self.mapView.carModelArray = [self selectCarWithType:sender.value];
        NSLog(@"%@",self.mapView.carModelArray);
    }
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    //collectionview 滚动结束后 调用
    - (void)selectItemArray:(NSArray *)array WithIndex:(NSInteger)index {
        
        MyAnnotation *an = self.mapView.annotationArray[index];
        [self.mapView.map selectAnnotation:an animated:YES];
        
    }
    
    #pragma mark - mapViewDelegate
    //点击地图没有点到大头针
    - (void)didSelectMapWithoutAnnotation {
        self.carPickView.hidden = NO;
        self.collectionView.hidden = YES;
    }
    
    //点到大头针
    - (void)didSelectMapAnnotationViewWithCarArray:(NSMutableArray<CarModel *> *)carArray WithIndex:(NSInteger)index {
        
        self.collectionView.carModelArray = carArray;
        NSLog(@"cararraycoutn =  %lu",(unsigned long)carArray.count);
        //跳转到选择的车辆信息
        [self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0] animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
    
        //出现车辆信息动画
        self.carPickView.hidden = YES;
        self.collectionView.hidden = NO; 
    }
    

    demo地址

    https://github.com/gongxiaokai/EasyCarDemo

  • 相关阅读:
    构建之法阅读笔记03
    周进度条
    周活动总结表
    电脑桌面美化
    如何让自己进步,去做成一件事
    后台网站
    laravel RBAC权限管理学习
    laravle定时任务
    django第一次简单讲解使用
    css3网页的淡入淡出效果
  • 原文地址:https://www.cnblogs.com/gongxiaokai/p/7123829.html
Copyright © 2011-2022 走看看