Gear VRで遊べる面白無料ゲーム6選

先日 Gear VR を購入したDayBySayです。 f:id:DayBySay:20160424170232j:plain

VRの世界、思ってた以上に楽しいです!楽しすぎて土日が潰れました!辛い。

ということで今回は、Gear VR本体について、あと私が遊んだコンテンツでオススメのものを紹介したいと思います。

ゲームパッドがなくても遊べます

Oculus Gear VR

Gear VRは、SamsungとOculusが共同開発しているスマホ装着型のVR用HMDです。

Gear VRの動作環境としては、Samsung製のスマートフォンである Galaxy S6 または S6 edge と、Gear VR の2つのデバイスが必要になります。

没入度が高いHMDの中では、比較的個人で購入しやすい価格レンジになっています( ¥15,000程度

Galaxy S6はだいたい ¥40,000 といったところでしょうか。

元からGalaxyユーザの人手あれば、気軽に環境を作れて良いですね!

詳しくはこちらの公式ページから www.samsung.com

オススメ無料ゲーム6選

前置きが長くなりましたが、オススメのゲームを紹介します!

ちなみに現在リリースされているコンテンツはほとんどが英語になっていますが、英語が読めなくても何となく遊べるのでご安心ください。笑

今回は、遊んでいて気になったプレイ時間と没入感と3D酔いの項目で簡単に評価しています。

Tenple Run VR

http://static.trustedreviews.com/94/000030ca7/a0d0/Temple-Run-VR.jpg

↑のモンスターが追いかけてくるので逃げるゲームです。笑

スマホゲーでお馴染みのTempleRunがGear VRでも遊べました。基本的に出来ることはスマホゲーと変わらないですが、振り向くと恐ろしいモンスターがいるので緊張感が半端ないです・・ 何分でも走れるので死ぬほど遊んでしまいました。

個人的には、途中スピードアップしてからはカーブでの3D良いが激しくなりました。3D酔いには個人差があると思いますが、このゲームを遊ぶ前にもう少し優し目のゲームで慣らしたほうが良いかなーと思います。

項目 度合い
プレイ時間 5分程度
没入感
3D酔い

Herobound: Gradiators

https://i.ytimg.com/vi/tXUbhlqokcw/maxresdefault.jpg

Herobound Gradiators 敵を倒しながらダンジョンを進んでいくアクションゲームで、最大4人までの協力プレイが可能になっています。私は使いませんでしたが、音声チャットで連絡を取り合いながら遊ぶことも出来るようです。

フィールドを跳ねまわりながら、剣や弓などの武器を持ち替えつつ協力して敵を倒していくのは爽快感があって楽しかったです。弓の攻撃方法に癖があって最初難しかったかな。笑

項目 度合い
プレイ時間 GearVRがヘタるまで
没入感
3D酔い

FindingVR

http://www.vrfocus.com/wp-content/uploads/2015/11/FindingVR-Header-1.png

FindingVRはファンタジックな世界をフックショットとフェアリーを駆使して飛び回るアクションゲームです(こう書くとゼルダっぽい)。ダンジョンの中に潜りながら時には敵を倒し、時には謎解きをしながらおくまで進むゲーム性は少年心をくすぐります。笑

ちょっと3D酔いしましたが、最後のボスも迫力満点で、VRのアクションゲームってこうだよね!というのをいち早く体験したい方にはオススメです。

項目 度合い
プレイ時間 20分程度
没入感
3D酔い

Protocol Zero デモ版

http://www.insidemobileapps.com/wp-content/uploads/2015/01/Protocol-Zero.jpg

Protocol Zeroはモバゲーでお馴染み(?)のDeNAが開発したFPSゲーム。デモ版では敵を倒しながら目的地まで到達するミッションが1つ遊べるようになっています。頭を傾きがゲーム内でも表現されており、壁からちょっとだけ頭を出すみたいなモーションも可能でかなり没入度が高い作品でした。さすがDeNA

いつか有料版も遊んでみようかなと思わせてくれるデモ版でした。

項目 度合い
プレイ時間 5分程度
没入感
3D酔い

InCell

http://cdn.akamai.steamstatic.com/steam/apps/396030/header.jpg?t=1460733824

InCellは人の細胞の中を走り回りつつ、ウィルスなどの破壊行為から細胞を護るカーレーシングゲームです。カテゴリとしては教育用のものらしく、楽しみながら人の細胞の仕組みを知ることが出来るゲームのようです。

こちらのゲームは今回体験した中でも一番3D良いが激しかったです。また、ゲーム内の移動がHMDの傾きで起きるのですが、頭を傾けるのと視点移動にギャップを感じて酔うのが辛かったです。ゲームパッドを使えることに気づいてからはだいぶマシでした。

TempleRunを遊んでも余裕だよーという人以外にはオススメできないです。

項目 度合い
プレイ時間 10分程度
没入感
3D酔い 超高

Shironeko VR Project

http://colopl.co.jp/products/oculus/images/150109_01_01.jpg

スマホゲーでお馴染みの白猫プロジェクトのVR版で、今回ご紹介するゲームの中で唯一日本語対応しているゲームです。こちらはソーシャル要素は無いみたいですね。ゲームパッドにも対応しており、操作性もよかったです。ただ視点が360度移動出来る事以外は、スマホゲーとくらべて新しい要素は無いように思いました。とはいえ元々素晴らしいゲームですし、今後の展開次第ではキラーアプリケーションになったりするのでは!?と期待させてくれるゲームなので、一度は遊んでみることをオススメします!

項目 度合い
プレイ時間 20分程度
没入感
3D酔い

Gear VR の雑感

VRゲームを長時間プレイしたのは初めてだったので、色々新しい発見がありました。

3D酔い

ゲームにもよりますが、3D酔いを起こしやすいものと起こしづらい物がありました。

特にレース系のゲームでスピードが早い奴は車酔いに近い状態になった・・ 視点の移動が激しい物は注意です。

遊ぶ場所を選ぶ

自分が向いてる方向がゲーム内の視点に影響するので、よく首を振ることになります。首だけならまだいいですが、体ごと後ろを振り返ろうとして椅子から落ちました。笑

遊ぶときは周りに危ないものがない状態でやるのがベターだと思います。

ゲームパッドが欲しくなる

基本的にはGear VR(とGalaxy)だけで遊べるものが多いですが、ゲームで使う入力のインタフェイスがGear VRの右側についているので、ずっと腕を上げていることになり疲れます。(首も結構疲れます

私は長時間遊びたかったのでゲームパッドを買いました。ゲームパッドを使い始めてからはかなり負担が減ったので、長時間ゲームをする際には必須に近いかなと感じています。

色々試したわけではないですが、私が使っていたゲームパッドは特に不自由もなく良かったのでおすすめです。もちろんGearVRのアプリでなくても利用可能です。

www.amazon.co.jp

以上!

Unityのコマンドライン引数を使ってパッケージ作成を自動化する

皆さんこんばんは。 思いが強くなりすぎて二郎を食べに新潟まで行ったDayBySayです。

本日はUnity用のプラグインを配布する際に使われるパッケージ(.unitypackage)作成を自動化する方法について書いていきます。

概要

Unityを用いて開発をされている方はご存知だと思いますが、Unityの世界では便利なライブラリやSDKなどをパッケージとして固めて利用する事がよくあると思います。

作られたパッケージは下記のようにAsset Store経由で配布されたり、GitHub上にホスティングされていたりと、配布方法はいくつか有りますが、形式はほとんどが.unitypackage形式だと思います。

基本的には、UnityのGUIを用いてパッケージの出力で作成することが可能ですが、これでは人為的ミスが入り込む余地が残ります。

そこで、今回はこの.unitypackageをの作成を、コマンドライン引数を用いて自動化したいと思います。 docs.unity3d.com

環境

Unity 5.3.2f1

Mac OSX 10.10.5

Projectのディレクトリ構造

$ ls
Assets          Library         Makefile        ProjectSettings README.md
$ tree Assets/
Assets/
├── Editors
│   ├── MyEditor.cs
│   └── MyEditor.cs.meta
├── Editors.meta
├── Plugins
│   ├── MyPlugin.cs
│   └── MyPlugin.cs.meta
├── Plugins.meta
├── Scripts
│   ├── MyScript.cs
│   └── MyScript.cs.meta
└── Scripts.meta

リポジトリ

リポジトリは下記です。 github.com

実装

今回はこんな感じで実装しました。

UNITY_CLI?=/Applications/Unity/Unity.app/Contents/MacOS/Unity
LOG_FILE?=unity.log
PROJ_PATH?=$(shell pwd)
PACKAGE_NAME?=MyPlugin.unitypackage
EXPORT_PACKAGES:= Assets/Editors Assets/Plugins Assets/Scripts

.PHONY: package

all:

package:
        $(UNITY_CLI) \
                -exportPackage $(EXPORT_PACKAGES) $(PACKAGE_NAME) \
                -projectPath $(PROJ_PATH) \
                -batchmode \
                -nographics \
                -logfile $(LOG_FILE) \
                -quit

オプションの説明

-exportPackage と -projectPath

-exportPackageは名前の通りファイル(ディレクトリ)をパッケージとしてエクスポートするためのオプションで、対象のプロジェクトを-projectPathで指定する必要があります。

パッケージに含める際に指定するディレクトリは、-projectPathで指定したプロジェクトから相対パスになります。

今回は-exportPackage Assets/Editors Assets/Plugins Assets/Scriptsとなるように指定していますが、今回のディレクトリ構造においては-exportPackage Assetsと等価です。

ちなみに-exportPackageはディレクトリのみ指定できます(ドキュメントをよく読まずファイルを指定しようとしてハマりました・・哀

-batchmode と -nographics

-batchmode を設定することで、GUIを使わず実行でき、-nographicsを設定することでグラフィックデバイスの初期化をしないため、GPUを積んでいないマシンでの実行が可能になります。 Unityを介した処理の自動化を行う場合は、基本的にこの2つのオプションを設定することになると思います。

-logfile

-logfileで実行結果をファイルに書きだしています。 成功しようが失敗しようがここにログが吐かれます。

-quit

バッチ処理終了後に自動でUnityを終了します。

これをしておかないと、処理完了後にUnityのプロセスが残ったままになってしまってうざい感じです。

インポートしてみる

ということで作成したパッケージをインポートしてみました。

f:id:DayBySay:20160322230856p:plain

指定したディレクトリ以下のファイルがすべてパッケージに含まれているのが確認できました。

まとめ

Unityのコマンドライン引数を使うと自動化が捗る。

あとは世の中のCIサービスがUnity対応してくれれば・・(;_;)

話題のSwift製WebフレームワークKituraをVagrantで手軽に試す

皆さんこんばんは。

腰を気にしていたら背中がやられてきたDayBySayです。

本日は今話題Swift製WebアプリケーションフレームワークKituraの開発環境を作ってみたいと思います。

概要

KituraIBMAppleのパートナーシップのもと制作されたらしいWebアプリケーションフレームワークです。

github.com

さらにIBM BlueMixというPaaSが完全対応しており、クライアントもサーバもSwiftという開発環境を作るにはもってこいであります。

私はまだ使ったこと無いですけど、この記事を読む限り、Push通知用のサーバも簡単に作れるらしい?

料金体系が計算ムズイ系だけれども、どうもPush通知回りは無料枠もあるっぽい。

Parseも死んじゃったし、色々大変だろうけどIBM Bluemixにもちょっとは期待してみよー。

ということで、話を戻すとIBM Blluemix上でも利用できるKituraを手軽に試す方法について書いていきます。

環境構築

READMEを見るとわかりますが、開発環境としてLinuxOSXDockerVagrantと、いたれりつくせりで色々用意してくれてます。

個人的な都合により、今回はOSX上でVagrantを利用する感じで進めたいと思います。

事前準備

VirtualBoxVagrantを入手します。

開発環境構築

まずは適当な場所にKituraをクローンします。

$ git clone git@github.com:IBM-Swift/Kitura.git

中身はこんな感じです

$ ls -la
total 120
drwxr-xr-x  17 t-sei  VOYAGEGROUP\Domain Users    578  3  1 01:57 .
drwxr-xr-x  44 t-sei  VOYAGEGROUP\Domain Users   1496  3  1 01:57 ..
drwxr-xr-x  13 t-sei  VOYAGEGROUP\Domain Users    442  3  1 01:57 .git
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users     16  3  1 01:57 .gitignore
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users   1073  3  1 01:57 .travis.yml
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users    649  3  1 01:57 .travis.yml.backup
drwxr-xr-x   7 t-sei  VOYAGEGROUP\Domain Users    238  3  1 01:57 Documentation
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users  10174  3  1 01:57 LICENSE.txt
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users    878  3  1 01:57 Makefile
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users   1405  3  1 01:57 Package.swift
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users   8197  3  1 01:57 README.md
drwxr-xr-x   3 t-sei  VOYAGEGROUP\Domain Users    102  3  1 01:57 Sources
drwxr-xr-x   3 t-sei  VOYAGEGROUP\Domain Users    102  3  1 01:57 Tests
-rwxr-xr-x   1 t-sei  VOYAGEGROUP\Domain Users   1161  3  1 01:57 buildTests.sh
-rwxr-xr-x   1 t-sei  VOYAGEGROUP\Domain Users   1510  3  1 01:57 build_osx.sh
-rwxr-xr-x   1 t-sei  VOYAGEGROUP\Domain Users    667  3  1 01:57 runTests.sh
-rw-r--r--   1 t-sei  VOYAGEGROUP\Domain Users   3097  3  1 01:57 vagrantfile

次に、Vgarantの環境を作ります... と言いたいところですが、実は2016/3/1現在、Kituraが提供しているVagrant環境は壊れています。

こちらのPR見るとわかりますが、どうもlibdispatch回りで不整合が起きてしまっているのが原因のようです。

ということで、今回はこのPRの元になっているブランチを利用してVagrantの開発環境を作りましょう。

$ git remote add cjwirth git@github.com:cjwirth/Kitura.git
$ git fetch cjwirth
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (43/43), done.
remote: Total 43 (delta 25), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (43/43), done.
From github.com:cjwirth/Kitura
 * [new branch]      develop    -> cjwirth/develop
 * [new branch]      fix-vagrant -> cjwirth/fix-vagrant
 * [new branch]      master     -> cjwirth/master
 * [new branch]      travis-ci-tests -> cjwirth/travis-ci-tests

$ git co fix-vagrant
Branch fix-vagrant set up to track remote branch fix-vagrant from cjwirth.
Switched to a new branch 'fix-vagrant'

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Fixed port collision for 22 => 2222. Now on port 2200.
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
(中略)
==> default: Compiling Swift Module 'KituraSample' (1 sources)
==> default: Linking Executable:  .build/debug/KituraSample
==> default: make[1]: Leaving directory '/home/vagrant/Kitura'

ここまでこれれば準備完了です。

サーバを立てる

まずはVagrant環境に入ります。

$ vagrant ssh
Welcome to Ubuntu 15.10 (GNU/Linux 4.2.0-30-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

そしておもむろに下記コマンドを叩きます。

$ Kitura/.build/debug/KituraSample
 INFO: spiListen(_:port:) /home/vagrant/Kitura/Packages/Kitura-net-0.2.0/Sources/KituraNet/HttpServerSpi.swift line 46 - Listening on port 8090

これでVagrant環境にサーバが立ちました。

初期設定をいじっていなければ、ホストのlocalhost:8090がゲストの8090にフォワーディングされる設定になっています。

確認してみましょう。

$ curl localhost:8091
You're running Kitura

成功っぽい! 環境ができたので今日はここまで。

まとめ

Kituraの提供しているVagrantの開発環境は現状壊れているので辛い。

Darwin-style フレームワーク内に他ライブラリ・フレームワークへの依存関係を記述する

皆さんこんばんは。

最近腰をやってしまったDayBySayです。

本日はDarwin-styleのフレームワーク内に依存関係を記述する方法についてまとめました。

概要

Mac OSXやiOSで利用されるDarwin-styleのフレームワークですが、サードパーティの誰かがフレームワークを提供する場合に、プラットフォームが提供するライブラリやフレームワークに対して依存関係を持つことが有ります。

例えば、Dropboxが提供しているSDK(フレームワーク)をダウンロードして利用する場合、こちらに書いてあるとおりSecurity.frameworkなどのフレームワークへのリンクをSDK利用側が明示的に行う必要があり、ちょっとめんどくさいです。

一方で、Googleが提供するAdMobのSDKダウンロードして利用する場合、フレームワークのリンクを明示的に行う必要はありません。

この違いは、ClangLink declarationを使っているかどうかになります。

Link declaration

先日の記事で書いたClangのモジュール機能の中に、Link declarationという依存関係を記述する機能が存在します。

この機能を使うことでフレームワーク側に依存関係を記述し、フレームワーク利用側の負担を減らすことが出来ます。

機能の説明は下記のようにされています。

Link declaration

A link-declaration specifies a library or framework against which a program should be linked if the enclosing module is imported in any translation unit in that program.

説明の通り、モジュールがライブラリ(共有ライブラリを指しています)とフレームワークに対してリンクを行うべきかを定義するものです。

記述方法は下記のような感じです。

link-declaration:

link framework opt string-literal

optframework記述に対してかかっています。

どういうことかというと、ライブラリ、例えばlibxml2へのリンクは

link xml2

であり、フレームワーク、例えばAVFoundation.frameworkへのリンクは

link framework AVFoundation

となる、ということです。

これらをmodule.modulemapファイルに記述することでフレームワーク側で依存関係の記述を吸収することが出来ます。

ちなみに先述のAdMob SDKmodule.moduemapはこんな内容になっています。

framework module GoogleMobileAds {
  umbrella header "GoogleMobileAds.h"

  export *
  module * { export * }

  link framework "AdSupport"
  link framework "AudioToolbox"
  link framework "AVFoundation"
  link framework "CoreGraphics"
  link framework "CoreMedia"
  link framework "CoreTelephony"
  link framework "EventKit"
  link framework "EventKitUI"
  link framework "Foundation"
  link framework "MessageUI"
  link framework "StoreKit"
  link framework "SystemConfiguration"
  link framework "UIKit"

// ~~ 略 ~~

}

実際に使ってみる

実際に使ってみたリポジトリこちらになります。

実際の処理は下記のような感じになっています

#import "HOGEFugaService.h"


@import AdSupport;

@implementation HOGEFugaService

// 中略

+ (NSString *)ADID {
    return [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
}

@end

今回は上記のようにASIdentifierManageradvertisingIdentifierメソッドフレームワーク内で利用しているため、このプロジェクトで作成されるHOGEFuga.frameworkAdSupport.frameworkへの依存を持つこととなります。

依存関係を記述するためのmodule.modulemapは下記のようになっています。

framework module HOGEFuga {
    umbrella header "HOGEFuga.h"

    export *
    module * { export * }

    link framework "AdSupport"
}

link framework "AdSupport"の記述を追加しました。

動かしてみる

HOGEFuga.frameworkを利用する側の実装は下記のようにしています。

import UIKit
import HOGEFuga

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        HOGEFugaService.show()
        NSLog("%@", HOGEFugaService.ADID())
    }

// 略
}

また、プロジェクトに対してAdSupport.frameworkのリンクを明示的に記述していません。

f:id:DayBySay:20160223220225p:plain

この状態でコンパイルできれば成功です。

Ctrl+Rでシミュレータ実行を行うと・・

f:id:DayBySay:20160223220345p:plain

コンパイル成功!そしてシミュレータのIDFAが出力されているので、動作確認も完了です!

まとめ

フレームワークを外部に対して提供する場合は、モジュールとして利用できる形にし、フレームワークへの依存を記述しておくと便利である。

CocoaPodsを使う場合リンク機能があるのでモジュールにしなくても済む場合があります

Unityプラグインとしてフレームワークを提供する場合、モジュール化とLink declarationが有効に働く気がします。

NSUserDefaultsのregisterDefaultsで登録された値を削除する

皆さんこんばんは。 優雅に京都でラーメンを食べているDayBySayです。

今回はタイトルの通りNSUserDefaultsregisterDefaultsで登録された値を削除することに関しての記事です。

モチベーション

当時開発していたアプリケーションで少々特殊な要件があり、UserAgentを柔軟に変えていくみたいな機能を実装する必要がありました。

iOS9以降でかつWKWebView限定に限定するのであればWKWebViewcustomUserAgentを設定するだけで済みますが、その時はiOS8以前もサポートしていたためNSUserDefaultsregisterDefaultsを使用しており、その場合において1度設定したカスタムなUserAgentからシステムの初期UserAgent状態に戻す方法がわかりませんでした。

ちなみに下記のようにUserDefaultsに設定を行うことでUserAgentのカスタマイズを行っています。

var webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75""

NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": "CustomUserAgent"])

webview = UIWebView.init() // registerDefaults後に初期化されたものからUAの変更が適用されます
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // "CustomUserAgent"

registerDefaultsで登録された値を初期化する

つまり、今回の要件としてはregisterDefaultsに登録された値を初期化すれば良いということになります。

普通に考えれば登録された値を削除するAPINSUserDefaultsにありそうな予感がしますが。。

NSUserDefaultsのリファレンスをみてみても、RegistaringDefaultsにはregisterDefaultsメソッドしか存在しません。

ふむ。。と思いつつも、もう少し探ってみるとregisterDefaultsの説明として下記のような記述がありました。

Discussion

If there is no registration domain, one is created using the specified dictionary, and NSRegistrationDomain is added to the end of the search list.

なるほど。registerDefaultsされた値はNSRegistrationDomainというドメインに保存されるらしいことがわかりました。

つまり、NSRegistrationDomainの値を書き換える(あるいは削除する)方法がわかれば対応できる可能性が出てきましたね。

NSRegistrationDomainに登録されている値を削除する

どんなドメインがあるかについてはこちらで多少説明されています。

NSRegistrationDomain

The domain consisting of a set of temporary defaults whose values can be set by the application to ensure that searches will always be successful.

NSRegistrationDomainは検索を必ず成功させるために設定されるものらしいです。 そういう意味では、値を簡単に削除できないようなインターフェイスになっているのは納得感が有りますね。

ただし今回は削除できないと困るので方法を探して見たところ、- setVolatileDomain:forName:というドメインの辞書をまるまる上書き出来そうなメソッドを見つけました。

後は、ドメインに登録されている辞書の一覧が取得できればなんとかなりそうですね!と、思いながら探すと- volatileDomainForName:というなにやら雰囲気のあるメソッドを見つけました。

説明は下記のようになっています。

Returns the dictionary for the specified volatile domain.

Return Value

The dictionary of keys and values belonging to the domain. The keys in the dictionary are names of defaults, and the value corresponding to each key is a property list object (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary).

ドメインの文字列を受け取って中身の辞書を返す、というように読み取ることが出来ます。

ということで実際に中身を見てみましょう

let registeredDefaults = NSUserDefaults.standardUserDefaults().volatileDomainForName(NSRegistrationDomain) // ["WebKitHiddenPageCSSAnimationSuspensionEnabled": 0, "WebKitMediaControlsScaleWithPageZoom": 0, "WebKitJavaScriptCanAccessClipboard": 0, "WebKitMediaPlaybackAllowsInline": 0, "WebKitLoadSiteIconsKey": 0, "WebKitDisplayImagesKey": 1, "WebKitAVKitEnabled": 1, "WebKitSuppressesIncrementalRendering": 0, "WebKitPictographFont": "AppleColorEmoji", "WebKitFantasyFont": "Papyrus", "WebKitWantsBalancedSetDefersLoadingBehavior": 0, "WebKitFrameFlatteningEnabled": 1, "WebKitDiagnosticLoggingEnabled": 0, "WebKitForceSoftwareWebGLRendering": 0, "WebKitSansSerifFont": "Helvetica", "AppleLanguages": ["en"], "WebKitSimpleLineLayoutDebugBordersEnabled": 0, "WebKitNetworkDataUsageTrackingEnabledPreferenceKey": 0, "WebKitExperimentalNotificationsEnabledPreferenceKey": 0, "WebKitPrivateBrowsingEnabled": 0, "WebKitUsesPageCachePreferenceKey": 1, "WebKitApplicationCacheDefaultOriginQuota": 26214400, "WebKitCursiveFont": "Snell Roundhand", "WebKitEnablePasswordEchoPreferenceKey": 1, "WebKitAcceleratedDrawingEnabled": 0, "WebKitSpatialNavigationEnabled": 0, "WebKitJavaScriptRuntimeFlagsPreferenceKey": 0, "WebKitStandardFont": "Times", "WebKitTelephoneParsingEnabledPreferenceKey": 0, "WebKitHistoryItemLimit": "1000", "WebKitStorageBlockingPolicy": 0, "WebKitNetworkInterfaceNamePreferenceKey": "", "WebKitInterpolationQualityPreferenceKey": 2, "WebKitCacheModelPreferenceKey": 0, "WebKitWebAudioEnabled": 1, "WebKitJavaScriptEnabled": 1, "WebKitApplicationCacheTotalQuota": 9223372036854775807, "WebKitUseLegacyTextAlignPositionedElementBehavior": 0, "WebKitPasswordEchoDurationPreferenceKey": 2, "WebKitUserStyleSheetEnabledPreferenceKey": 0, "AntialiasedFontDilationEnabled": 0, "WebKitEditableLinkBehavior": 0, "WebKitAudioSessionCategoryOverride": 0, "UserAgent": "CustomUserAgent", "WebKitLocalFileContentSniffingEnabledPreferenceKey": 0, "WebKitShowRepaintCounter": 0, "WebKitHiddenPageDOMTimerThrottlingEnabled": 0, "WebKitFixedFont": "Courier", "WebKitPageCacheSupportsPluginsPreferenceKey": 1, "WebKitUseSiteSpecificSpoofing": "0", "NSLanguages": ["en"], "WebKitJavaScriptMarkupEnabled": 1, "WebKitCSSRegionsEnabled": 1, "WebKitHyperlinkAuditingEnabled": 1, "WebKitAllowAnimatedImageLoopingPreferenceKey": 1, "WebKitLocalStorageEnabledPreferenceKey": 1, "WebKitWebArchiveDebugModeEnabledPreferenceKey": 0, "WebKitLowPowerVideoAudioBufferSizeEnabled": 0, "WebKitDeveloperExtrasEnabledPreferenceKey": 0, "WebKitAllowAnimatedImagesPreferenceKey": 1, "WebKitCSSCompositingEnabled": 1, "WebKitSerifFont": "Times", "WebKitQTKitEnabled": 0, "WebKitDefaultFixedFontSize": "13", "WebKitDOMTimersThrottlingEnabledPreferenceKey": 1, "WebKitLayoutIntervalPreferenceKey": -1, "WebKitAsynchronousSpellCheckingEnabled": 0, "WebKitPlugInSnapshottingEnabled": 0, "WebKitOfflineWebApplicationCacheEnabled": 0, "WebKitHistoryAgeInDaysLimit": "7", "WebKitAccelerated2dCanvasEnabled": 0, "WebKitDNSPrefetchingEnabled": 0, "WebKitSubpixelCSSOMElementMetricsEnabled": 0, "WebKitShouldRespectImageOrientation": 1, "WebKitDatabasesEnabledPreferenceKey": 1, "WebKitAllowFileAccessFromFileURLs": 1, "NSInterfaceStyle": "macintosh", "WebKitAuthorAndUserStylesEnabledPreferenceKey": 1, "WebKitAcceleratedCompositingEnabled": 1, "WebKitShowDebugBorders": 0, …, "WebKitFullScreenEnabled": 0, "WebKitWebSecurityEnabled": 1, "WebKitAlwaysRequestGeolocationPermission": 0, "WebKitAllowMultiElementImplicitFormSubmissionPreferenceKey": 0, "WebKitMaxParseDurationPreferenceKey": -1, "WebKitMinimumZoomFontSizePreferenceKey": 15, "WebKitXSSAuditorEnabled": 1, "WebKitEnableInheritURIQueryComponent": 0, "WebKitShrinksStandaloneImagesToFit": 0, "WebKitWebGLEnabled": 1, "WebKitBackForwardCacheExpirationIntervalKey": "1800", "WebKitAllowUniversalAccessFromFileURLs": 1, "WebKitUsesEncodingDetector": 0, "WebKitDefaultFontSize": "16", "WebKitPluginsEnabled": 1, "WebKitMinimumFontSize": "0", "WebKitCanvasUsesAcceleratedDrawing": 0, "WebKitMinimumLogicalFontSize": "9", "WebKitMediaPlaybackRequiresUserGesture": 1, "WebKitDefaultTextEncodingName": "ISO-8859-1"]

なるほど、なんかそれっぽいのが返ってきている!

すげーわかりづらいけど真ん中らへんに"UserAgent": "CustomUserAgent"を見つけたので、これらはregisterDefaultsで登録された辞書の値だということがわかりました。

ということは、- volatileDomainForName:で辞書を取得し、特定のキー(今回は'UserAgent')を削除し、- setVolatileDomain:forName:で辞書を登録すれば行けそうな雰囲気を感じます。

ということでやってみました。

var webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75""

// UAの登録
NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": "CustomUserAgent"])

webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // "CustomUserAgent"

// 登録されたUAの削除
var registeredDefaults = NSUserDefaults.standardUserDefaults().volatileDomainForName(NSRegistrationDomain)
registeredDefaults.removeValueForKey("UserAgent")
NSUserDefaults.standardUserDefaults().setVolatileDomain(registeredDefaults, forName: NSRegistrationDomain)

webview = UIWebView.init()
webview.stringByEvaluatingJavaScriptFromString("navigator.userAgent") // ""Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13C75"" <- 戻ってる!

ヒュー!

まとめ

NSUserDefaultsregistrationDefaultsで登録した値を消したい時は- volatileDomainForName:で辞書を取得し、特定のキーを削除し、- setVolatileDomain:forName: で上書き登録し直す。

The following build commands failed: CompileStoryboard ProjectName/Base.lproj/LaunchScreen.storyboard

TravisCIでXcodeのプロジェクトをビルドしようとした時に発生したエラー。

まさかのStoryBoardコンパイル失敗。笑

結論、TravisCIのXcodeServer環境と手元の環境に差があるのが原因のようだったので、osx_imageを指定することでエラー回避。

language: objective-c
osx_image: xcode7.2
script: make test

TravisCIのXcode環境は地獄だぜ・・ きっとXcodeServerの対応が大変なんだろうなぁと妄想しています。

余談

私はTravisCI上でxcodebuildの出力をxcprettyを利用して整形するようにしているのですが、出力だけをパイプで渡すとxcodebuildがエラーを吐いてもCIがpassしてしまいます。

↓こんな感じ

** BUILD FAILED **


The following build commands failed:
    Check dependencies
(1 failure)

The command "make test" exited with 0.

Done. Your build exited with 0.

この場合は下記のようにPIPESTATUSを引き継いで出力してあげると正しく動作してくれます。

test:
    xcodebuild -project ProjectName.xcodeproj  |  xcpretty -c && exit $${PIPESTATUS[0]} # Makefileの中なので$が2重