Swiftの横スクロースができるUIコンポーネントPageMenuを使ってみた

横スクロールができるコンポーネントPageMenuを使ってみた

環境情報
Xcode 7.2.1
iOS 9.2

今回創るページはこんな感じ。

以下のページを参考にした。
Swift 横スクロール画面切り替えを簡単に実装する。 - Qiita

1.PageMenuのファイルを取得

githubのページからプロジェクトファイルを取得する
PageMenu

プロジェクト毎Zipファイルをダウンロードするか、以下のファイルだけでもいい。
Classes/CAPSPageMenu.swift

2.Xcodeでプロジェクト作成

SingleViewApplicationを選択してプロジェクトを作成する。
使用言語はswiftを選択。

3.プロジェクトにPageMenuのファイルを追加

1で取得したファイルを解凍し、以下のファイルをXcodeのプロジェクトに追加する
Classes/CAPSPageMenu.swift

4.PageMenu関連のコードを追記

PageMenuの変数を宣言
class ViewController: UIViewController {
    var pageMenu : CAPSPageMenu?
}
PageMenuに追加するViewを宣言

viewDidLoad関数の中に記述

// Viewを格納する配列
var controllerArray : [UIViewController] = []

// 追加するViewを作成
let controller1 : UIViewController = UIViewController()
controller1.title = "ビューその1"
controller1.view.backgroundColor = UIColor.blueColor()
controllerArray.append(controller1)

let controller2 : UITableViewController = UITableViewController()
controller2.title = "ビューその2"
controller2.view.backgroundColor = UIColor.redColor()
controllerArray.append(controller2)

let controller3 : UITableViewController = UITableViewController()
controller3.title = "ビューその3"
controller3.view.backgroundColor = UIColor.greenColor()
controllerArray.append(controller3)
PageMenuの設定とビューの追加

こちらもviewDidLoad関数の中に記述

// PageMenuの設定
let parameters: [CAPSPageMenuOption] = [
    .MenuItemSeparatorWidth(4.3),
    .UseMenuLikeSegmentedControl(true),
    .MenuItemSeparatorPercentageHeight(0.1)
]

// PageMenuへのビューの追加と、PageMenuのビューサイズを設定
pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 20.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)

// PageMenuのビューを親のビューに追加
self.view.addSubview(pageMenu!.view)

画面の下部にUIToolbarを追加する

PageMenuにToolbarを追加してみる。

StoryBoardでToolbarを追加し、制約でLeft、Rightは-20、Bottomは0に設定。

このままだとToolbarが見えないので、PageMenuのビューをToolbarの後ろに移動

// PageMenuのビューを親のビューに追加
self.view.addSubview(pageMenu!.view)
// PageMenuのビューをToolbarの後ろへ移動    ←追加
self.view.sendSubviewToBack(pageMenu!.view) ←追加

できあがりはこんな感じ。

ソースはこちら

GitHub - yuriken27/PageMenuSample

ViewControlle.swiftの内容

import UIKit

class ViewController: UIViewController {
    
    var pageMenu : CAPSPageMenu?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Viewを格納する配列
        var controllerArray : [UIViewController] = []
        
        // 追加するViewを作成
        let controller1 : UIViewController = UIViewController()
        controller1.title = "ビューその1"
        controller1.view.backgroundColor = UIColor.blueColor()
        controllerArray.append(controller1)
        
        let controller2 : UITableViewController = UITableViewController()
        controller2.title = "ビューその2"
        controller2.view.backgroundColor = UIColor.redColor()
        controllerArray.append(controller2)
        
        let controller3 : UITableViewController = UITableViewController()
        controller3.title = "ビューその3"
        controller3.view.backgroundColor = UIColor.greenColor()
        controllerArray.append(controller3)
        
        // PageMenuの設定
        let parameters: [CAPSPageMenuOption] = [
            .MenuItemSeparatorWidth(4.3),
            .UseMenuLikeSegmentedControl(true),
            .MenuItemSeparatorPercentageHeight(0.1)
        ]
        
        // PageMenuのビューのサイズを設定
        pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRectMake(0.0, 20.0, self.view.frame.width, self.view.frame.height), pageMenuOptions: parameters)
        
        // PageMenuのビューを親のビューに追加
        self.view.addSubview(pageMenu!.view)
        // PageMenuのビューをToolbarの後ろへ移動
        self.view.sendSubviewToBack(pageMenu!.view)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

node.js + express + socket.io でスライドショーのシンクロアプリを作成

概要

複数デバイス間でスライドを同期する方法はないか・・・という話があったので、
node.js + express + socket.ioを使って、スライドショーの同期アプリを作ってみた。

こんな感じで左の画面でスライドを変更すると、右の画面も同期して自動的にスライドが変わる。
f:id:yuriken27:20150301222506j:plain

タブレットを想定していたので、Webアプリにして各種ブラウザで実行できる様にした。

デモサイトはこちら

SyncSlideTest

表示のみで操作できないバージョンはこちら
SyncSlideTest

スライドショーのライブラリを選定

スライドショーのライブラリは以下を使用。


Supersized - Full Screen Background Slideshow jQuery Plugin

細かい設定ができて使いやすい。

各種バージョン

node.js :0.10.36
express : 4.11.2
socket.io : 1.3.4

expressでプロジェクト作成

テンプレートはejsを使うため、「-e」を追加して実行

$ express -e slide_sync

   create : slide_sync
   create : slide_sync/package.json
   create : slide_sync/app.js
   create : slide_sync/public
   create : slide_sync/public/images
   create : slide_sync/public/javascripts
   create : slide_sync/public/stylesheets
   create : slide_sync/public/stylesheets/style.css
   create : slide_sync/routes
   create : slide_sync/routes/index.js
   create : slide_sync/routes/users.js
   create : slide_sync/views
   create : slide_sync/views/index.ejs
   create : slide_sync/views/error.ejs
   create : slide_sync/bin
   create : slide_sync/bin/www

   install dependencies:
     $ cd slide_sync && npm install

   run the app:
     $ DEBUG=slide_sync:* ./bin/www

[package.json]にsocket.ioの定義を追加

{
...

  "dependencies": {
...
    "serve-favicon": "~2.2.0",
    "socket.io": "~1.3.4"
  }
}

プロジェクトフォルダに移動してインストールを実行

$ cd slide_sync/
$ npm install

app.jsの編集

app.jsに以下を追加

// サーバモジュール作成
var server = require('http').Server(app);
var io = require('socket.io')(server);

var port = process.env.PORT || 3000;
server.listen(port);


// スライド操作関連
var current_slide = 0;  // 現在のスライド番号を保持

//クライアントから接続があった時
io.sockets.on('connection', function (socket) {

  // コネクションが確立されたら'connected'メッセージを送信する
  console.log("[connection] has received current_slide:[" + current_slide + "]");
  socket.emit('connected', {value: current_slide});

  // メッセージ送信(送信者にも送られる)
  socket.on("C_to_S_message", function (data) {
    console.log("[C_to_S_message] has received");
    current_slide = data.value;
    io.sockets.emit("S_to_C_message", {value: current_slide});
  });

  // ブロードキャスト(送信者以外の全員に送信)
  socket.on("C_to_S_broadcast", function (data) {
    console.log("[C_to_S_broadcast] has received");
    current_slide = data.value;
    socket.broadcast.emit("S_to_C_message", {value: current_slide});
  });

  // 切断したときに送信
  socket.on("disconnect", function () {
    console.log("[disconnect] has received");
  });
});

スライドhtmlを変更

supersizedライブラリの[slide.html]をベースに、views/index.ejsを作成。

サーバとのやりとり関連の処理を追加

<script type="text/javascript">

var s = io.connect('http://young-hollows-5254.herokuapp.com'); //heroku
// var s = io.connect('http://localhost:3000'); //ローカル

//サーバから受け取るイベント
s.on("connect", function () {});  // 接続時
s.on("connected", function (data) {
  addMessage(data.value);
});  // 接続時
s.on("disconnect", function (client) {});  // 切断時
s.on("S_to_C_message", function (data) {
  addMessage(data.value);
});

//クライアントからイベント送信(イベント名は自由に設定できます)
function sendMessage(slide_index) {
  s.emit("C_to_S_message", {value: slide_index}); //サーバへ送信
}

function sendBroadcast(slide_index) {
  s.emit("C_to_S_broadcast", {value: slide_index}); // サーバへ送信
}

//jqueryでメッセージを追加
function addMessage (value,color,size) {
  api.goTo(value + 1);
}

</script>

ページ切り替えの時にサーバとの通信を行う。

<!--Arrow Navigation-->
<a id="prevslide" class="load-item" onclick="click_prevslide();"></a>
<a id="nextslide" class="load-item" onclick="click_nextslide();"></a>
function click_prevslide() {
  var slide_num = $.supersized.vars.options.slides.length;
  sendMessage( ( ($.supersized.vars.current_slide - 1 + slide_num) % slide_num ) );
}

function click_nextslide() {
  var slide_num = $.supersized.vars.options.slides.length;
  sendMessage( ( ($.supersized.vars.current_slide + 1 + slide_num) % slide_num ) );
}

herokuにデモサイドをアップロード

heroku用の実行コマンドファイル[Procfile]を作成

[Procfile]を作成

web: node app.js

herokuにアップロード

$ heroku create
$ git add .
$ git commit -m "first commit"
$ git push heroku master

iPhoneからAndroidのXperia Z Ultraに変えたら意外とすんなり移行できた

iPhoneからXperia

ipod Touchから始まり、iPhone4→iPhone4SiPhone5Siphoneユーザでしたが、
おおきな画面にしたくてiPhone6 Plusを検討していたけど・・・

・10万?高い!
・SIMフリー売ってない!

と言うことで、いろいろ検討していたらこちらを発見!


Xperia Z Ultra | Smartphone - Sony Smartphones (Global UK English)

お値段も4万円を切るお手頃な価格。

てことで、これ買いました。

Xperia Z Ultra使ってみて

Android端末をメイン端末として使うのは初めて。

スクロールで端に来たときにiPhoneではバウンドするのが
Androidではピタッと止まるので違和感があったけど、
動きの軽快さや操作への追随など、ほとんど気にならないレベル。

Androidもここまで進化していたのか!

メリット

なんと言っても画面が大きい

6.4inch画面はiPhone6 Plusよりも一回り大きい

Webやfacebookの閲覧が快適すぎて小さい画面に戻れない
画面サイズが大きいからブログの編集もある程度可能だけど、
文字入力関連(キーボードなど)が操作しづらいので長文はつらい

防水・防塵機能完備

水中に落としても無事だった例もあるくらいの防水機能付き
雨くらいなら余裕ですな

結構薄い

ズボンの後ろポケットに入れて持ち運んでます。
今のところ曲がってはいない。

動きもサクサク

サクサク動いて反応もGood!

デメリット

プラスエリア未対応

プラスエリア未対応なので、電波が弱いところで繋がらないことあり
都内では屋外では気にならないが、地下の店で奥に座ったりすると圏外になることもしばしば

近々プラスエリア対応に挑戦する予定

キー入力など片手操作はできない

両手でキーボードのレイアウトで入力してます

音が悪い

MacBookProや初代iPad(古い?)と比べると音質はいまいち
音が割れるので映像はともかく、音楽を聴く気にはならない

まとめ

デメリットもあるけどおおむね満足。
お手頃価格で大画面のスマホとしてはかなりオススメ!


パープルもいいな!

IIJmioおすすめポイント

IIJmioとは

最近増えてきた、MVNOのひとつ。

DoCoMoの回線を使っており、契約するとDoCoMo SIMが渡されるので、
基本的にDoCoMoSIM対応のスマホがそのまま使える。

IIJmioを使ってみて

・通信速度が速い
 東京の東新宿と池袋近辺で速度計測をすると、
 だいたいダウンロードで15Mbpsくらいでる。
 遅くても8Mbpsくらい。

・他社と同じくらい安い
 通信量 2GB/月 通話、SMS付きプランで1600円(税別)

また、低速モードだと月々のデータ容量を消費せずに通信可能。
通信速度200kbpsだがアンテナたってても通信できない他社より快適な気も。

IIJmioに変えるとお得な人

現在DoCoMoスマホを使っている人

SIMフリーの端末を持っている人もOK!

電話をかける事が少ない人(LINE等は除く)

電話代が高いので・・・

毎月の通信量が2~4GBの人

7GBのプランでも安いけど、お得感は少なめ

※ドコモユーザーは月々の通信量はMyDoCoMoで確認できる

MNPできるので電話番号を引き継げるのもポイント

デメリット

電話代が高い

G-callなどを併用することをおすすめ

メールアドレスがもらえない

携帯メールでのやりとりはできない
電話番号を使ったSMSはできる

MyDoCoMoで年齢認証ができない

機種変更したときに、Lineで再度認証が必要になったができなかった・・・

swiftで現在地情報と文字列から目的地情報を取得し、現在地と目的地を結ぶ経路を取得してマップに表示

作成する機能

・現在地の位置情報を取得
・目的地の文字列から目的地の位置情報を取得
・現在地と目的地を結ぶ経路情報を取得
・地図上にこれらを表示

作成するアプリの処理内容

ユーザが検索バーに目的地の文字列を入力し、検索ボタンをタップすると現在地と目的地にピンを立て、経路を表示する

1.目的地の文字列から目的地の位置情報を取得
2.現在地の位置情報を取得
3.現在地から目的地までの経路を取得
4.現在地、目的地、経路を地図に表示

実行結果は以下(お茶の水から新宿までの経路)
f:id:yuriken27:20150202224534p:plain

前作業

まずは作業用プロジェクトを作成する

・新規プロジェクトの作成からSingleViewApplicationを選択
・ProjectNameに好きな名前を入力
・Languageは「Swift」を選択

1.目的地の文字列から目的地の位置情報を取得

位置情報の取得許可を設定するため、Supporting Files の Info.plistを右クリックして、
「Open as」-「Source Code」で開いて以下の4行を追加

Info.plist

<plist version="1.0">
<dict>

・・・ここから・・・
    <key>NSLocationAlwaysUsageDescription</key>
    <string>I have learned more on stack overflow than anything else</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>The spirit of stack overflow is coders helping coders</string>
・・・ここまで・・・

</dict>
</plist>

各種Delegateを設定

class定義のUIViewControllerのあとに、以下のDelegateを設定を追加

【, CLLocationManagerDelegate, MKMapViewDelegate, UISearchBarDelegate】

ViewController.swift

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UISearchBarDelegate {
・・・
}

・CLLocationManagerDelegate : 位置情報取得用
・MKMapViewDelegate : マップ用
・UISearchBarDelegate : 検索バー用

それぞれのDelegateにメインビューのアドレス(self)をセットしておかないと
Delegateメソッドが呼ばれないので注意。

ViewController.swift

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        locationManager = CLLocationManager()
        locationManager.delegate = self
        mapView.delegate = self
        destSearchBar.delegate = self
    }

2.現在地の位置情報を取得

位置情報取得用のコードを追加

【locationManager.startUpdatingLocation()】を実行した後に呼び出されるDelegateメソッドを記述

現在地の測位が終わったら以下のメソッドのどちらかが呼ばれる
  成功時:func locationManager(manager: CLLocationManager!,didUpdateLocations locations: [AnyObject]!)
  失敗時:func locationManager(manager: CLLocationManager!,didFailWithError error: NSError!)


ViewController.swift

    // 位置情報取得に成功したときに呼び出されるデリゲート.
    func locationManager(manager: CLLocationManager!,didUpdateLocations locations: [AnyObject]!){

        userLocation = CLLocationCoordinate2DMake(manager.location.coordinate.latitude, manager.location.coordinate.longitude)
        
        // 現在地の取得に成功したら、ピンを立てておく
        var userLocAnnotation: MKPointAnnotation = MKPointAnnotation()
        userLocAnnotation.coordinate = userLocation
        userLocAnnotation.title = "現在地"  // ピンをタップしたときに表示される文字を指定
        mapView.addAnnotation(userLocAnnotation)
    }

    // 位置情報取得に失敗した時に呼び出されるデリゲート.
    func locationManager(manager: CLLocationManager!,didFailWithError error: NSError!){
        print("locationManager error")
    }


検索バーの検索ボタンを押したときに現在地の取得を開始する場合は以下のように記述

ViewController.swift

    // 検索ボタンを押したときにキーボードを隠して検索実行
    func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        locationManager.startUpdatingLocation()
    }

3.現在地から目的地までの経路を取得

目的地の文字列から位置情報を取得

検索バーの文字列(destSearchBar.text)から目的地の位置情報を取得する

ViewController.swift

    func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        // キーボードを隠す
        destSearchBar.resignFirstResponder()

        // 目的地の文字列から座標検索
        var geocoder = CLGeocoder()
        geocoder.geocodeAddressString(destSearchBar.text, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
            if let placemark = placemarks?[0] as? CLPlacemark {
                // 目的地の座標を取得
                self.destLocation = CLLocationCoordinate2DMake(placemark.location.coordinate.latitude, placemark.location.coordinate.longitude)
                // 目的地にピンを立てる
                self.mapView.addAnnotation(MKPlacemark(placemark: placemark))
                // 現在地の取得を開始
                self.locationManager.startUpdatingLocation()
            }
        })
    }

現在地と目的地を結ぶ経路情報を取得

現在地(userLocation)と目的地(destLocation)を指定して経路情報を取得

ViewController.swift

    func getRoute()
    {
        // 現在地と目的地のMKPlacemarkを生成
        var fromPlacemark = MKPlacemark(coordinate:userLocation, addressDictionary:nil)
        var toPlacemark   = MKPlacemark(coordinate:destLocation, addressDictionary:nil)

        // MKPlacemark から MKMapItem を生成
        var fromItem = MKMapItem(placemark:fromPlacemark)
        var toItem   = MKMapItem(placemark:toPlacemark)

        // MKMapItem をセットして MKDirectionsRequest を生成
        let request = MKDirectionsRequest()

        request.setSource(fromItem)
        request.setDestination(toItem)
        request.requestsAlternateRoutes = false // 単独の経路を検索
        request.transportType = MKDirectionsTransportType.Any

        let directions = MKDirections(request:request)
        directions.calculateDirectionsWithCompletionHandler({
            (response:MKDirectionsResponse!, error:NSError!) -> Void in

            response.routes.count
            if (error != nil || response.routes.isEmpty) {
                return
            }
            var route: MKRoute = response.routes[0] as MKRoute
            // 経路を描画
            self.mapView.addOverlay(route.polyline!)
            // 現在地と目的地を含む表示範囲を設定する
            self.showUserAndDestinationOnMap()
        })
    }

4.現在地、目的地、経路を地図に表示

現在地、目的地を含む範囲を表示範囲にして地図に表示する

ViewController.swift

    // 地図の表示範囲を計算
    func showUserAndDestinationOnMap()
    {
        // 現在地と目的地を含む矩形を計算
        var maxLat:Double = fmax(userLocation.latitude,  destLocation.latitude)
        var maxLon:Double = fmax(userLocation.longitude, destLocation.longitude)
        var minLat:Double = fmin(userLocation.latitude,  destLocation.latitude)
        var minLon:Double = fmin(userLocation.longitude, destLocation.longitude)

        // 地図表示するときの緯度、経度の幅を計算
        var mapMargin:Double = 1.5;  // 経路が入る幅(1.0)+余白(0.5)
        var leastCoordSpan:Double = 0.005;    // 拡大表示したときの最大値
        var span_x:Double = fmax(leastCoordSpan, fabs(maxLat - minLat) * mapMargin);
        var span_y:Double = fmax(leastCoordSpan, fabs(maxLon - minLon) * mapMargin);

        var span:MKCoordinateSpan = MKCoordinateSpanMake(span_x, span_y);

        // 現在地を目的地の中心を計算
        var center:CLLocationCoordinate2D = CLLocationCoordinate2DMake((maxLat + minLat) / 2, (maxLon + minLon) / 2);
        var region:MKCoordinateRegion = MKCoordinateRegionMake(center, span);

        mapView.setRegion(mapView.regionThatFits(region), animated:true);
    }

    // 経路を描画するときの色や線の太さを指定
    func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
        if overlay is MKPolyline {
            var polylineRenderer = MKPolylineRenderer(overlay: overlay)
            polylineRenderer.strokeColor = UIColor.blueColor()
            polylineRenderer.lineWidth = 5
            return polylineRenderer
        }
        return nil
    }

実行イメージ

こんな感じで経路がちょうど収まる倍率で地図を表示する。
f:id:yuriken27:20150202224543p:plain
f:id:yuriken27:20150202225033p:plain

今回作ったプロジェクトはこちら
yuriken27/MapViewDirectionsTestSwift · GitHub

ViewController.swift の内容

ViewController.swift

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate, UISearchBarDelegate {

    var locationManager: CLLocationManager!
    var userLocation: CLLocationCoordinate2D!
    var destLocation: CLLocationCoordinate2D!

    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var destSearchBar: UISearchBar!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        locationManager = CLLocationManager()
        locationManager.delegate = self
        mapView.delegate = self
        destSearchBar.delegate = self

        // 位置情報取得の許可状況を確認
        let status = CLLocationManager.authorizationStatus()

        // 許可が場合は確認ダイアログを表示
        if(status == CLAuthorizationStatus.NotDetermined) {
            println("didChangeAuthorizationStatus:\(status)");
            self.locationManager.requestAlwaysAuthorization()
        }
        //位置情報の精度
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        //位置情報取得間隔(m)
        locationManager.distanceFilter = 300
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 検索ボタンを押したときにキーボードを隠して検索実行
    func searchBarSearchButtonClicked(searchBar: UISearchBar) {
        // キーボードを隠す
        destSearchBar.resignFirstResponder()
        // セット済みのピンを削除
        self.mapView.removeAnnotations(self.mapView.annotations)
        // 描画済みの経路を削除
        self.mapView.removeOverlays(self.mapView.overlays)
        // 目的地の文字列から座標検索
        var geocoder = CLGeocoder()
        geocoder.geocodeAddressString(destSearchBar.text, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
            if let placemark = placemarks?[0] as? CLPlacemark {
                // 目的地の座標を取得
                self.destLocation = CLLocationCoordinate2DMake(placemark.location.coordinate.latitude, placemark.location.coordinate.longitude)
                // 目的地にピンを立てる
                self.mapView.addAnnotation(MKPlacemark(placemark: placemark))
                // 現在地の取得を開始
                self.locationManager.startUpdatingLocation()
            }
        })
    }

    // 位置情報取得に成功したときに呼び出されるデリゲート.
    func locationManager(manager: CLLocationManager!,didUpdateLocations locations: [AnyObject]!){

        userLocation = CLLocationCoordinate2DMake(manager.location.coordinate.latitude, manager.location.coordinate.longitude)

        var userLocAnnotation: MKPointAnnotation = MKPointAnnotation()
        userLocAnnotation.coordinate = userLocation
        userLocAnnotation.title = "現在地"
        mapView.addAnnotation(userLocAnnotation)
        // 現在地から目的地家の経路を検索
        getRoute()
    }

    // 位置情報取得に失敗した時に呼び出されるデリゲート.
    func locationManager(manager: CLLocationManager!,didFailWithError error: NSError!){
        print("locationManager error")
    }

    func getRoute()
    {
        // 現在地と目的地のMKPlacemarkを生成
        var fromPlacemark = MKPlacemark(coordinate:userLocation, addressDictionary:nil)
        var toPlacemark   = MKPlacemark(coordinate:destLocation, addressDictionary:nil)

        // MKPlacemark から MKMapItem を生成
        var fromItem = MKMapItem(placemark:fromPlacemark)
        var toItem   = MKMapItem(placemark:toPlacemark)

        // MKMapItem をセットして MKDirectionsRequest を生成
        let request = MKDirectionsRequest()

        request.setSource(fromItem)
        request.setDestination(toItem)
        request.requestsAlternateRoutes = false // 単独の経路を検索
        request.transportType = MKDirectionsTransportType.Any

        let directions = MKDirections(request:request)
        directions.calculateDirectionsWithCompletionHandler({
            (response:MKDirectionsResponse!, error:NSError!) -> Void in

            response.routes.count
            if (error != nil || response.routes.isEmpty) {
                return
            }
            var route: MKRoute = response.routes[0] as MKRoute
            // 経路を描画
            self.mapView.addOverlay(route.polyline!)
            // 現在地と目的地を含む表示範囲を設定する
            self.showUserAndDestinationOnMap()
        })
    }

    // 地図の表示範囲を計算
    func showUserAndDestinationOnMap()
    {
        // 現在地と目的地を含む矩形を計算
        var maxLat:Double = fmax(userLocation.latitude,  destLocation.latitude)
        var maxLon:Double = fmax(userLocation.longitude, destLocation.longitude)
        var minLat:Double = fmin(userLocation.latitude,  destLocation.latitude)
        var minLon:Double = fmin(userLocation.longitude, destLocation.longitude)

        // 地図表示するときの緯度、経度の幅を計算
        var mapMargin:Double = 1.5;  // 経路が入る幅(1.0)+余白(0.5)
        var leastCoordSpan:Double = 0.005;    // 拡大表示したときの最大値
        var span_x:Double = fmax(leastCoordSpan, fabs(maxLat - minLat) * mapMargin);
        var span_y:Double = fmax(leastCoordSpan, fabs(maxLon - minLon) * mapMargin);

        var span:MKCoordinateSpan = MKCoordinateSpanMake(span_x, span_y);

        // 現在地を目的地の中心を計算
        var center:CLLocationCoordinate2D = CLLocationCoordinate2DMake((maxLat + minLat) / 2, (maxLon + minLon) / 2);
        var region:MKCoordinateRegion = MKCoordinateRegionMake(center, span);

        mapView.setRegion(mapView.regionThatFits(region), animated:true);
    }

    // 経路を描画するときの色や線の太さを指定
    func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! {
        if overlay is MKPolyline {
            var polylineRenderer = MKPolylineRenderer(overlay: overlay)
            polylineRenderer.strokeColor = UIColor.blueColor()
            polylineRenderer.lineWidth = 5
            return polylineRenderer
        }
        return nil
    }
}

IIJmioに替えてから1ヶ月の所感

1ヶ月の使用状況

データ通信量:1.5GB
音声通話:30分(IIJmioの電話料金は高いのでG-CALLを使用)
使用料金:IIJmioの2GBプランが1600円、通話料が500円くらいで、合計は税込み2300円ほど。

使用感

電波のつかみ具合

DoCoMoの電波を使っているせいか、繋がらないと思うことはそんなにない。

データ通信速度

通信速度は東京都豊島区や新宿区でダウンロードが8Mbps〜26Mbps、
アップロードが3Mbps〜9Mbps。

ダウンロードでは大体15Mbps程度は出るから結構速い!

電話

G-CALLを使って電話をかけているせいか、相手の電話が鳴るまで少し(2秒くらい)時間がかかる
音質はDoCoMoの時と変わらないと思う。

テザリング

基本的な設定を行えば問題無くテザリング可能。
使用感はDoCoMoの頃と変わらない。

データ通信の使用感

IIJmioでは、速度制限がない高速通信と最大200kbpsの低速通信の2種類があり、
高速通信で使えるデータ容量は契約しているプランのデータ容量になる。

高速/低速の切り替えはIIJmioが出しているアプリで随時切り替え可能で
低速通信ならデータ通信の残容量が減らないので、うまく使えば通信量を節約できる。

低速モードでもそれほどストレスが無いアプリ

Facebookアプリ
 どうやっているか分からないけど、結構ストレスなし。

・LINE
 文字だけなら問題なし。画像や動画がある場合は遅くなるけど。

・メール受信
 Gmailを使っているけど高速通信でも少し遅い。低速通信だともう少し遅いがそんなに気にならない。
 HTMLメールで画像が多いとやっぱり遅い。

Radiko
 普通に使える。

高速通信じゃないとつらいアプリ

・Webブラウズ
文字情報メインだと問題無いが、画像がたくさんあるページが多いので、高速モードがおすすめ

・マップアプリ
高速モードでないと読み込みが遅すぎて実用的じゃない

その他

スマホを変更したときにLINEの年齢認証が必要になったが、IIJmioでは認証できなかった

・アプリのアップデートは自宅や公衆無線LANwifi経由でやるようにして通信量を節約する

まとめ

DoCoMoの頃は月2GBから3GBくらいの使用量だったけど、細かく節約すれば2GB未満は可能


AWSでRedmineをセットアップ時の記録

AWSセットアップ
使ったAMI
Amazon Linux AMI 2014.03.1

インスタンスのセットアップ中にConfigure Security GroupでHTTPを追加

MySQLセットアップ
# yum install mysql mysql-server mysql-devel

[mysqld]セクションに以下を追加

/etc/my.cnf
character-set-server=utf8
skip-character-set-client-handshake
default-storage-engine=innodb
collation-server=utf8_general_ci
innodb_file_format=Barracuda
innodb_file_per_table=1
MySQLを起動
$ sudo service mysqld start

ユーザ作成、DB作成
# mysql -uroot

mysql> CREATE USER 'user'@'localhost' IDENTIFIED BY '********';
mysql> create database dbname;
mysql> GRANT ALL PRIVILEGES ON dbname.* TO user@localhost ;

■モジュールインストール
# yum install mysql-devel
# yum install ImageMagick ImageMagick-devel -y
# yum install httpd httpd-devel -y

# gem install bundler

Redmine セットアップ
# cd /home/
# mkdir redmine
# cd redmine/
# wget http://www.redmine.org/releases/redmine-2.5.1.tar.gz
# tar xvzf redmine-2.5.1.tar.gz
# cd redmine-2.5.1
$ bundl install

nokogiriでエラーが出たので以下を実行

$ sudo yum install gcc*
$ sudo yum install -y gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel
※この2行は必要ないかも。。。

# bundle config build.nokogiri --use-system-libraries

$ bundle install

$ rake generate_session_store

  • > Note: The rake task generate_session_store has been deprecated, please use the replacement version generate_secret_token

古いコマンドだよとのことなので、エラーメッセージに従って以下のコマンドを実行。

$ rake generate_secret_token
$ rake db:migrate RAILS_ENV=production
$ rake redmine:load_default_data RAILS_ENV=production

Redmineの設定

# vim [redmine_root]/config/email.yml

[redmine_root]/config/email.yml
production:
delivery_method: :async_smtp
smtp_settings:
address: localhost
port: 25
domain: domainname.com
# vim [redmine_root]/config/database.yml

[redmine_root]/config/database.yml
production:
adapter: mysql2
database: redmine
host: localhost
username: user
password: "********"
encoding: utf8
■Passengerインストール
# gem install passenger --no-rdoc --no-ri
# yum install curl-devel

passenger-install-apache2-moduleを実行したら警告が出たので、事前に以下のコマンドを実行しておく。
# sudo dd if=/dev/zero of=/swap bs=1M count=1024
# sudo mkswap /swap
# sudo swapon /swap

# passenger-install-apache2-module

最後に表示される以下の情報をコピーしておく。

LoadModule passenger_module /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45/buildout/apache2/mod_passenger.so

PassengerRoot /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45
PassengerDefaultRuby /usr/bin/ruby2.0


ServerName www.yourhost.com
# !!! Be sure to point DocumentRoot to 'public'!
DocumentRoot /somewhere/public

# This relaxes Apache security settings.
AllowOverride all
# MultiViews must be turned off.
Options -MultiViews
# Uncomment this if you're on Apache >= 2.4:
#Require all granted


Passengerの設定をhttpd.confに追加
# vim /etc/httpd/conf/httpd.conf

/etc/httpd/conf/httpd.conf
LoadModule passenger_module /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45/buildout/apache2/mod_passenger.so

PassengerRoot /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45
PassengerDefaultRuby /usr/bin/ruby2.0

Passenger.confの設定
Redmineを任意のフォルダで動作させるためにはRackBaseURIの指定が必要

# vim /etc/httpd/conf.d/passenger.conf

/etc/httpd/conf.d/passenger.conf
# LoadModule passenger_module /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45/buildout/apache2/mod_passenger.so
PassengerRoot /usr/local/share/ruby/gems/2.0/gems/passenger-4.0.45
PassengerDefaultRuby /usr/bin/ruby2.0

PassengerMaxPoolSize 20
PassengerMaxInstancesPerApp 4
PassengerPoolIdleTime 3600
PassengerHighPerformance on
PassengerStatThrottleRate 10
PassengerSpawnMethod smart
RailsAppSpawnerIdleTime 86400
PassengerMaxPreloaderIdleTime 0
PassengerResolveSymlinksInDocumentRoot on

RackBaseURI /redmine
redmineに【ドメイン名/redmine】でアクセスするため、シンボリックリンク作成
シンボリックリンク作成
# ln -s /home/redmine/redmine-2.5.1/public/ /var/www/html/redmine

apacheに権限付与
# cd /home/redmine
# chown -R apache:apache redmine-2.5.1

apache再起動
# service httpd start