読者です 読者をやめる 読者になる 読者になる

エンジニアリングとお金の話

都内で働くエンジニアの日記です。投資と技術の話題がメインです。

【技術】iphoneアプリを初めて作ってみた

技術

以前から、iphoneアプリを作成してみたいと思って技術書を読んだりネットで調べて見たりと色々と調査を行っていたが、やっぱり作らないと覚えないという事で実際に簡単なアプリを作成してみた。

作成したアプリの内容は、ワンクリックで現在開いている店が分かり、店を選択すると現在地からの経路が表示されるという内容である。画面遷移はこんな感じ。

1.初期画面
f:id:hatakazu93:20140629231733p:plain
2.開いてる店一覧
f:id:hatakazu93:20140629231812p:plain
3.経路検索画面
f:id:hatakazu93:20140629231838p:plain

処理内容としては、リクルートWebサービスが提供しているホットペッパーAPIにて情報を取得し、お店一覧を作成し、iosのMKMapViewにて経路画面を作成している。xcodeに中々慣れなくて正直へこたれそうになったが、xvimを入れてキーバウンドをvimと同じにしたらタイピングが凄く楽になった。もう、vim無しではコーディング出来ない体になりつつある。

アプリ作成についてだが、まずstorybordで画面を作成した。
f:id:hatakazu93:20140629233403p:plain
今回は、view Controllerとtable viewとmap viewを使用した。また、画面遷移はsegueを使用している。画面遷移の仕方が良く分からず悩んだが、分かってしまえば意外と簡単であった。segueを使用した画面遷移の仕方はこんな感じである。
f:id:hatakazu93:20140629234947p:plain

次はコーディングだが、今回は作成しなければならない機能は大きく以下の3つである。
1.ホットペッパーAPIより値を取得する。
2.取得した情報をTable Viewに表示する。
3.経路情報をMap Viewに表示する。

で、APIのリクエストについては素でリクエストを行うのはしんどかったので、オープンソースライブラリのAFNetworkingを使用した。AFNetworkingはデフォルトでJSONを扱う様になっているので、パース済みのデータを受け取る事が出来て便利である。

また、画面間で値を受け渡しする際はprepareForSegue:(UIStoryboardSegue *)segue sender:(id)senderメソッドにて画面遷移イベントが発生した際にsegue名を判断して遷移先画面のプロパティに値を設定する様にしている。なんかここらへんはイマイチな気がするがしょうが無い。

ちなみに、作成したPGはこんな感じである。

ViewController.m

//
//  ViewController.m
//  Test
//
//  Created by hatakazu on 2014/06/16.
//  Copyright (c) 2014年 moge. All rights reserved.
//

#import "ViewController.h"
#import <AFNetworking/AFNetworking.h>

@interface ViewController (){
    IBOutlet CLLocationManager *locationManager;
    NSMutableArray *shopArray;
    float fromLatitude;
    float fromLongitude;
}
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
   
}

-(IBAction)push
{
    //現在地の経度、緯度を取得
    if([CLLocationManager locationServicesEnabled])
    {   
        locationManager=[[CLLocationManager alloc]init];
        locationManager.delegate=self;
        locationManager.desiredAccuracy=kCLLocationAccuracyHundredMeters;
        locationManager.activityType=CLActivityTypeFitness;
        locationManager.pausesLocationUpdatesAutomatically=NO;
        locationManager.distanceFilter=100;
        [locationManager startUpdatingLocation];
    }else{
        NSLog(@"ERROR");
    }   
}

-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CLLocation *newLocation=[locations lastObject];
    
    fromLatitude=newLocation.coordinate.latitude;
    fromLongitude=newLocation.coordinate.longitude;
    
    [self getJSONData:[NSString stringWithFormat:@"%f",newLocation.coordinate.latitude] longitude:[NSString stringWithFormat:@"%f",newLocation.coordinate.longitude]];
}

//ホットペッパーAPIにリクエスト処理を実施
-(void)getJSONData:(NSString *)latitude longitude:(NSString *)longitude
{
    AFHTTPRequestOperationManager *manager=[AFHTTPRequestOperationManager manager];
    [manager GET:@"http://webservice.recruit.co.jp/hotpepper/gourmet/v1/"
      parameters:@{
                    @"key":@"APIキー",
                    @"format":@"json",
                    @"lat":latitude,
                    @"lng":longitude,
                    @"range":@"5",
                    @"is_open_time":@"now"
                   }
      success:^(AFHTTPRequestOperation *operatin,id responseObject)
      {
          shopArray=[NSMutableArray new];         
          NSDictionary *allData=responseObject[@"results"];
          
          for(NSDictionary *shopDict in allData[@"shop"]){
             [shopArray addObject:shopDict];
          }
          
          //ToShopセグエを使用して画面遷移
          [self performSegueWithIdentifier:@"ToShop" sender:self];
      }
      failure:^(AFHTTPRequestOperation *operatin,NSError *error)
      {
         NSLog(@"Error:%@",error);
      }];
}

//画面遷移の際に処理を実施
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    //ToShopセグエにて画面遷移が行われた際の処理
    if([[segue identifier] isEqualToString:@"ToShop"])
    {
        //次画面へ受け渡す値の設定
        NextViewController *nextview = [segue destinationViewController];
        nextview.shopArray=shopArray;
        nextview.fromLatitude=fromLatitude;
        nextview.fromLongitude=fromLongitude;
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

NextViewController.m

//
//  NextViewController.m
//  Test
//
//  Created by hatakazu on 2014/06/24.
//  Copyright (c) 2014年 moge. All rights reserved.
//

#import "NextViewController.h"

@interface NextViewController ()
{
    int selectIndex;
}

@end

@implementation NextViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.shopArray count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:@"shopCell"];
    UILabel *label=(UILabel *)[cell viewWithTag:1];
    label.text=self.shopArray[indexPath.row][@"name"];
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    selectIndex=indexPath.row;
    [self performSegueWithIdentifier:@"ToMap" sender:self];
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"ToMap"])
    {
        MapViewController *mapview = [segue destinationViewController];
        mapview.mapArray = self.shopArray[selectIndex];
        mapview.fromLatitude=self.fromLatitude;
        mapview.fromLongitude=self.fromLongitude;
    }
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

MapViewController.m

//
//  MapViewController.m
//  Test
//
//  Created by hatakazu on 2014/06/28.
//  Copyright (c) 2014年 moge. All rights reserved.
//

#import "MapViewController.h"

@interface MapViewController ()
{
    
IBOutlet MKMapView *mapView;
    
}

@end

@implementation MapViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    CLLocationCoordinate2D centorCoordinate = CLLocationCoordinate2DMake(self.fromLatitude, self.fromLongitude);
    MKCoordinateSpan span = MKCoordinateSpanMake(0.003, 0.003);
    mapView.region=MKCoordinateRegionMake(centorCoordinate, span);
}

-(void)viewDidAppear:(BOOL)animated
{
    NSString *toLatiude = self.mapArray[@"lat"];
    NSString *toLongitude = self.mapArray[@"lng"];
    
    CLLocationCoordinate2D fromCoordinate = CLLocationCoordinate2DMake(self.fromLatitude,self.fromLongitude);
    CLLocationCoordinate2D toCoordinate = CLLocationCoordinate2DMake(toLatiude.floatValue,toLongitude.floatValue);
        
    MKPlacemark *fromPlacemark = [[MKPlacemark alloc] initWithCoordinate:fromCoordinate addressDictionary:nil];
    MKPlacemark *toPlacemark = [[MKPlacemark alloc] initWithCoordinate:toCoordinate addressDictionary:nil];
    
    MKMapItem *fromItem = [[MKMapItem alloc] initWithPlacemark:fromPlacemark];
    MKMapItem *toItem = [[MKMapItem alloc] initWithPlacemark:toPlacemark];
    
    MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
    request.source = fromItem;
    request.destination = toItem;
    request.requestsAlternateRoutes = YES;
    request.transportType=MKDirectionsTransportTypeWalking;
    
    MKDirections *directions = [[MKDirections alloc] initWithRequest:request];

    [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response,NSError *error)
     {
         if(error) return;
         if([response.routes count]>0)
         {
             MKRoute *route = [response.routes objectAtIndex:0];
             [mapView addOverlay:route.polyline];
             
         }
         
     }];
    
}

-(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{
    if([overlay isKindOfClass:[MKPolyline class]])
    {
        MKPolyline *route = overlay;
        MKPolylineRenderer *routeRenderer = [[MKPolylineRenderer alloc] initWithPolyline:route];
        routeRenderer.lineWidth = 5.0;
        routeRenderer.strokeColor = [UIColor redColor];
        return routeRenderer;
    }else{
        return nil;
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end

実際に作成してみると、色々と分からなかった部分が大分理解出来た。特にデリゲートについて理解出来たのは大きく、やっぱりプログラミングは手を動かすべきだとつくづく感じた。今回のアプリ作成を初めの一歩として、もっと複雑なアプリを作成出来るようにしていきたい。

広告を非表示にする