検索
連載

OptionalなどSwiftとObjective-Cの違いを意識してPhotos Frameworkを使うiPhone 6/6 Plusアプリ開発入門(4)(3/3 ページ)

iPhone 6/6 PlusアプリをSwift言語で作成してみたいという初心者向けにiOS 8の新機能を使ったアプリの作り方を一から解説する入門連載。今回は、前回の記事で追加した写真の情報を「Photos Framework」の機能を使用して取り出し、写真の情報にひも付いた座標にピンを表示します。また、その過程でOptionalなどSwiftとObjective-Cの違いについて解説します。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

MapViewに注釈オブジェクトを追加する

 Optional型の説明が長くなってしまいましたが、ここで再びViewController.swiftファイルの実装に戻ります。

 「prepareAnnotations」メソッドの内容は以下の通りでした。

private func prepareAnnotations() {
    let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: nil)
    fetchResult?.enumerateObjectsUsingBlock ({result, index, stop in
        if let asset = result as? PHAsset {
            if let location = asset.location {
                let annotation = MKPointAnnotation()
                annotation.coordinate = location.coordinate
                self.mapView.addAnnotation(annotation)
            }
        }
    })
}

Optional Chaining

 3〜11行目の「fetchResult?.enumerateObjectsUsingBlock ({ ... })」では「Optional Chaining」を使用しています。「Optional Chaining」は「Optional型」や「Implicitly Unwrapped Optional型」の変数やメソッドに接尾辞「?」を付けて安全にアクセスできる仕組みです。

 「Optional型」の変数に接尾辞「!」を付けて行うアンラップは、変数の内容にかかわらず中身を取り出すものでした。それに対し「Optional Chaining」は、変数が値を持つ場合は、その次の処理を継続し、変数の中身がnilの場合は処理を中断してnilを返します。

 「prepareAnnotations」メソッド内の「fetchResult?.enumerateObjectsUsingBlock ({ ... })」の場合、fetchResultの中身がnilでない場合は、「enumerateObjectsUsingBlock」メソッドが実行され、fetchResultの中身がnilの場合は処理が終了します。

引数が「クロージャ」の「enumerateObjectsUsingBlock」メソッド

 次に、PHFetchResultクラスのenumerateObjectsUsingBlockメソッドを見ていきます。

 PHFetchResultオブジェクトは、フォトライブラリから取得した写真の情報を保持していて、enumerateObjectsUsingBlockメソッドを使用することで写真の情報を一つずつ取り出せます。

 enumerateObjectsUsingBlockメソッドの定義は以下の通りです。

func enumerateObjectsUsingBlock(block: ((AnyObject!, Int, UnsafeMutablePointer<ObjCBool>) -> Void)!)

 enumerateObjectsUsingBlockメソッドのblock引数は「クロージャ」になっています。クロージャは「値として扱える関数」のようなもので、メソッドの引数として渡すことができます。

 enumerateObjectsUsingBlockメソッドに渡すクロージャ内には、写真情報一つを取り出した際に行う処理を記述しておきます。その処理は写真情報を取り出したときに実行されます。最終的に取り出せたデータ分の処理が行われます。

 一つ目の引数resultに写真情報一つが入ってきます。resultの型はAnyObject!型(AnyObjectは任意のクラスのインスタンスを表す型)です。

 クロージャ内のif文(4〜10行目)の条件式では、「型チェック」と「型キャスト」を行い「Optional Binding」を使用しています。最終的にPHAsset型のオブジェクトが取り出せた場合のみ真の処理が実行されます。

fetchResult?.enumerateObjectsUsingBlock ({result, index, stop in           
    if let asset = result as? PHAsset {
        // PHAsset型のオブジェクトが取り出せた場合の処理
    }
})

注釈オブジェクトの作成

 さらに内側のif文(5〜9行目)でも「Optional Binding」を使用しています。外側のif文で取り出したPHAsset型のオブジェクトから「CLLocation」型のオブジェクトを取り出し、注釈オブジェクトを作成して、MapViewへ追加しています。

 これで、フォトライブラリから取得したPHAssetオブジェクトが保持する位置にピンが追加されます。

fetchResult?.enumerateObjectsUsingBlock ({result, index, stop in
    if let asset = result as? PHAsset {
        if let location = asset.location {
            // CLLocation型のオブジェクトが取り出せた場合の処理
            // 注釈オブジェクトを作成
            let annotation = MKPointAnnotation()
            annotation.coordinate = location.coordinate
            // MapViewへ追加
            self.mapView.addAnnotation(annotation)
        }
    }
})

ViewController.swiftの最終形

 途中で寄り道してしまったので、ViewController.swiftの全コードを載せておきます。

 本記事の内容を実装していくと、ViewController.swiftのコードは以下のようになります。

import UIKit
import MapKit
import Photos
 
class ViewController: UIViewController {
    @IBOutlet weak var mapView: MKMapView!
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        self.prepareMapView()
        self.checkAuthorizationStatus()
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    private func prepareMapView() {
        self.mapView.rotateEnabled = false
        self.mapView.pitchEnabled = false
        
        let centerCoordinate = CLLocationCoordinate2D(latitude: 35.681382, longitude: 139.766084)
        let initialSpan = MKCoordinateSpan(latitudeDelta: 0.4, longitudeDelta: 0.4)
        let initialRegion = MKCoordinateRegion(center: centerCoordinate, span: initialSpan)
        self.mapView.setRegion(initialRegion, animated: true)
    }
    
    private func checkAuthorizationStatus() {
        let status = PHPhotoLibrary.authorizationStatus()
        
        switch status {
        case .Authorized:
            self.prepareAnnotations()
        default:
            PHPhotoLibrary.requestAuthorization{ status in
                if status == .Authorized {
                    self.prepareAnnotations()
                }
            }
        }
    }
    
    private func prepareAnnotations() {
        let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: nil)
        fetchResult?.enumerateObjectsUsingBlock ({result, index, stop in
            
            if let asset = result as? PHAsset {
                if let location = asset.location {
                    let annotation = MKPointAnnotation()
                    annotation.coordinate = location.coordinate
                    self.mapView.addAnnotation(annotation)
                }
            }
        })
    }
}

動作確認

 RunボタンをクリックしてiOSシミュレーターで動かしてみましょう。


図1 iOSシミュレーター(iPhone 4S)の実行結果(初回起動直後)

 初回起動時の場合、フォトライブラリへのアクセスの許可を求めるアラートが表示されるので、「OK」をクリックします。


図2 iOSシミュレーター(iPhone 4S)の実行結果(アラート選択後)

 アラートが消えると、MapView上にピンが追加されるはず……ですが、「注釈オブジェクトの追加」と「ピンの描画」のタイミングがちょっと合ってないみたいです。MapViewを任意の方向へスワイプさせて、MapViewの表示範囲を少し変更してみてください。


図3 iOSシミュレーター(iPhone 4S)の実行結果(MapViewの中心位置移動後)

 MapViewの中心位置が移動すると、ピンが描画されるかと思います(この不具合は今後修正しましょう)。

次回は、実際の写真の取得してピンと差し替える

 今回は、前回の記事で追加した写真情報を「Photos Framework」の機能を使用して取り出し、写真情報にひも付いた座標にピンを表示しました。また、Swiftの特徴の一つである「Optional型」について解説しました。

 次回も引き続き「PhotoMap」の実装を進めていきます。今回の記事で写真情報を基にピンを表示できたので、次回は実際の写真を取得し、標準のピンを写真のサムネイルに差し替えていきます。

著者紹介

平屋真吾

クラスメソッド株式会社 iPhoneアプリサービス事業部所属のプログラマーです。iOSアプリの開発がメインですが、デザインやAWSなども勉強中です。

ブログ:http://dev.classmethod.jp/author/hiraya-shingo/


前のページへ |       

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る