iPadのマルチタスク(SlideOver, SplitView)対応
iOS9からiPadでmultitaskingが使えるようになります。 SlideOverはiPad Air以降、SplitViewはiPad Air2以降で利用できます。
SlideOver, SplitViewに対応したアプリの作成方法
新規で作成する場合
Xcode7で新しいプロジェクトを作ると特に何もしなくてもよいです
既存のアプリを対応させる場合
下記の条件をすべて満たすことが必要
- Xcode7(iOS9SDK)でビルド
- launch screen storyboardを使う
- iPadですべての回転方向をサポートする
ドキュメント
Adopting Multitasking Enhancements on iPad: Slide Over and Split View Quick Start
Swift2でStringを指定した文字で分割
beta5でsplitの仕様がちょっと変わり、以下のようにすることで文字列を分割できます。
let string1 = "hoge" let string2 = string1.characters.split("o").map{ String($0) } // [h, ge]
Swift2からStringはCollectionTypeではなくなり、Stringの保持するcharactorsがCollectionTypeになっています
Strings in Swift 2 - Swift Blog - Apple Developer
Swift1系
struct String { init() } extension String : CollectionType {
Swift2
extension String { /// A `String`'s collection of `Character`s ([extended grapheme /// clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)) /// elements. public struct CharacterView { /// Create a view of the `Character`s in `text`. public init(_ text: String) } /// A collection of `Characters` representing the `String`'s /// [extended grapheme /// clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster). public var characters: String.CharacterView { get } ... extension String.CharacterView : CollectionType, Indexable, SequenceType { /// A character position.
splitはCollectionTypeのメソッドで仕様は以下のようになっています。
CollectionType Protocol Reference
charactors.split('separator') だけだと結果はCharacterViewのArrayとなってしまうので、map{ String($0) } でStringに変換しています。
SwiftでArray内のOptionalをunwrapする
なにを言っているのかよくわからないタイトルになってますが、
Array<T?>をArray
いろいろやり方はありますが、一番手短にかける方法は このようにflatMapにかけるだけ
let array1: [String?] = ["1", nil, "2"] let array2 = array1.flatMap{ $0 }
というのも、mapとflatMapはinterfaceにも微妙な違いがあるため
func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
func flatMap<T>(@noescape transform: (Self.Generator.Element) -> T?) -> [T]
iOS9 ATSの設定方法
β4現在
Xcode7でビルドするとiOS9以降でApp Transport Security (ATS) が利用可能となります
App Transport Security Technote: App Transport Security Technote
デフォルトでhttps通信が必須となるので、この挙動を変更するには上の仕様に書いてあるようにInfo.plistに設定を記載する必要があります。
ATS設定パターン
1. 全ての通信にApp Transport Securityを適用
設定不要
2. 全ての通信にApp Transport Securityを適用しない
以下のように設定することでhttpsでなくても通信可能となります
3. 指定したドメインのみ適用する
指定したドメインのみhttps通信をmustにしたい場合は以下のように設定します サブドメインも含めたい場合は、 NSIncludesSubdomainsをYESにします。
この例では、yahoo.co.jpとそのサブドメインに対して通信する場合はhttpsが必須となり、 その他のサイトでは任意となります。
4. 指定したドメイン以外に適用する
指定したドメインのみhttps通信をmustにしたく”ない”場合は以下のように設定します
この例では、yahoo.co.jpのみhttpで通信可能となり、それ以外はhttpsが必須となります。
これ以外の細かい設定はAppTransportSecurityTechnoteに色々とパラメータが用意されているので、それらを参照ください
Xcode7 betaでiOS8.4端末にインストールできない場合
Xcode7 beta3で、iOS8.4の端末をつなげても"ineligible devices"となり、端末が選択できず実機にインストールできない。。 (追記:beta4ではiOS8.4に対応してます)
調べてみると8.4が入ってないです
$ ls /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/ 6.0/ 7.0/ 8.1/ 8.3/ 6.1/ 7.1/ 8.2/ 9.0 (13A4254u)/
解決方法としては、Xcode6.4から8.4をコピーしてくればとりあえず動きました
$ cp -r /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/8.4\ \(12H141\) /Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/
XcodeのSwiftのFile Templateを変更する
Xcodeでデフォルトで生成される以下のようなtemplateを変更したい場合は
// // File.swift // Project_name // // Created by xxxxxxxxxxx on xxxx/xx/xx. // Copyright © 2015年 xxxxxxxxx xxxxxxx. All rights reserved. // import Foundation
このようにユーザーのHomeディレクトリ配下にTemplateファイルを配置してやり、
$ mkdir -p ~/Library/Developer/Xcode/Templates/File\ Templates/User\ Templates $ cd ~/Library/Developer/Xcode/Templates/File\ Templates/User\ Templates $ cp -r /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File\ Templates/Source/Swift\ File.xctemplate .
FILEBASENAME_.swiftを変更すれば、Xcodeでファイルを作る際に、このようにUser Templateが現れるので、ここのSwift Fileを選択すれば独自のtemplateを使うことができます。
AppCodeだったらこんな面倒なことしなくてもtemplate変更できるんですが・・
Swift2でのbitmask
Swift2からRawOptionSetTypeがOptionSetType (OptionSetType Protocol Reference) に変わったため、
例えばUIViewAutoresizingの場合、 これまでは
view?.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
だったのが
self.view?.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
になります。
OptionSetTypeのprotocol extensionでいくつかメソッドが用意されているので、これらを使って、and, or, exclusive orなどのbit演算を行ったり、containsを使ってbitが立っているかチェックできます
extension OptionSetType { /// Returns the set of elements contained in `self`, in `other`, or in /// both `self` and `other`. func union(other: Self) -> Self /// Returns the set of elements contained in both `self` and `other`. func intersect(other: Self) -> Self /// Returns the set of elements contained in `self` or in `other`, /// but not in both `self` and `other`. func exclusiveOr(other: Self) -> Self } extension OptionSetType where Element == Self { /// Returns `true` if `self` contains `member`. /// /// - Equivalent to `self.intersect([member]) == [member]` func contains(member: Self) -> Bool /// If `member` is not already contained in `self`, insert it. /// /// - Equivalent to `self.unionInPlace([member])` /// - Postcondition: `self.contains(member)` mutating func insert(member: Self) /// If `member` is contained in `self`, remove and return it. /// Otherwise, return `nil`. /// /// - Postcondition: `self.intersect([member]).isEmpty` mutating func remove(member: Self) -> Self? }
Vagrantでjavascriptやcssのファイルを変更しても反映されない
Vagrantでjavascriptやcssを変更しても反映されないという問題にはまりました。
ブラウザのキャッシュの問題かと思ったのですが、Chrome Developer ToolのSettingで"Disable cache"にしても変化なし。
ならばサーバー側だということでnginxのキャッシュとかを調べていたら、nginxとかの問題ではなく、VirtualBoxのBugのようで、VirtualBox(Vagrant)とshareしているフォルダにおいてるとだめぽい。
http://docs.vagrantup.com/v2/synced-folders/virtualbox.html
ここに書いてあるようにsendfileをoffにすればなおります。
nginx
sendfile off;
EnableSendFile off
Ansibleでユーザーにグループを追加してもすぐに反映されない
Ansibleでユーザーにグループを追加するのは以下のようなやり方でできます
user - Manage user accounts — Ansible Documentation
- user: name=hoge group=test append=yes
が、この後に、hogeユーザーで、testのグループパーミッションのついたファイルやディレクトリを操作しようとするとなぜかpermission deniedになってしまいます。
調べたところissueにのっていて、 どうもflush the connectionを実装してもらわないと無理ぽい Impossible to add oneself to a group to use this group permission · Issue #921 · ansible/ansible-modules-core · GitHub
なので、グループパーミッションを利用して制限かけるのは諦めて他の方法でやるしかない
AnsibleでVagrantのprovisioningする際のgitの認証を通すには
ここに書いてあるやり方でいけました。
AnsibleからVagrantにssh経由でgit cloneする時の注意点 - Qiita
が、あまり関係ないと思っていた
sudo: no
が結構重要だったりしました。
gitを実行するTaskをsudoで実行してしまうと、userが変わってしまうのでssh forward agentが効かなくなってしまうっぽいので、gitなどのtaskはsudoをnoにしておく必要があります。
VagrantとAnsibleでFuelPHP開発環境構築(Ubuntu 14.04, nginx)
事前準備
- Vagrantのインストール
- Ansibleのインストール
ここではこれらのインストール方法は割愛
環境構築
とりあえず開発環境作りたい場合は、以下を実行し、しばらくすると環境が構築されます。
$ git clone https://github.com/yanamura3/FuelEnv $ cd FuelEnv $ vagrant up
localhost:3334をブラウザで開いてFuelPHPの画面がでればOKです。 FuelPHPでの開発はFuelEnv以下にfuelというフォルダができているのでそれを使えばよいです。
詳細
いじったところなどのメモ
Vagrant
VagrantFile
# -*- mode: ruby -*- # vi: set ft=ruby : VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "ubuntu/trusty64" config.vm.provider "virtualbox" do |vb| # Customize the amount of memory on the VM: vb.memory = "2048" end config.vm.network "forwarded_port", guest: 80, host: 3334 config.vm.synced_folder "./", "/var/www" config.vm.provision "ansible" do |ansible| ansible.playbook = "provisionings/site.yml" ansible.limit = "default" ansible.groups = { "vagrant" => ["default"], } end end
config.vm.synced_folderでカレントディレクトリとVM側の/var/www以下を同期するようにしています。 これはあとでFuelPHPを/var/www以下にcloneしてきて、PHPStormで開発しやすくするためにこうしました。
config.vm.provision "ansible" do |ansible| ansible.playbook = "provisionings/site.yml"
このように書くことで簡単にVagrantからansibleのplaybookを実行することができます。(これによってinvetoryの設定など面倒なことをVagrant側がやってくれます)
ansible.limit = "default" ansible.groups = { "vagrant" => ["default"], }
この部分は、VagrantからAnsibleを実行されたときのみで条件をわけるためにこうしています。host名はVagrantではdefaultになっていました。
Ansible
Ansibleというか、Fuelを使う上での設定周りではまったところで、主にnginx周りです。 Ansibleに関してはansible-example(ansible/ansible-examples · GitHub)をベースにやるとやりやすかったです。
Why Ansible
サーバー側に何もいれなくてよいので、knife-soloでやるよりはこっちでいいかなくらいのノリです。
nginx
nginx.confのほうはsites-enableのconfigファイルを参照するように変更
/etc/nginx/nginx.conf
include /etc/nginx/sites-enable/*.conf
virtualhostの設定自体はsites-available以下において、sites-enable以下にそれに対するシンボリックリンクを置くようにしました。(不要なときにシンボリックリンクだけ消せばよいので
/etc/nginx/sites-available/virtual.conf
server { listen 80 default_server; server_name localhost; root /var/www/fuel/public; location / { try_files $uri /index.php?$uri&$args; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }
rootを/var/www/fuel/publicとpublicフォルダ以下になるように設定。
とくにlocationのところが重要で、localhost:xxxxでwelcomeページは開けるけど、localhost:xxxx/helloが開けない という場合はここの記述が間違っている可能性が高いです。
fastcgiを動かすために、fastcgi_passを127.0.0.1:9000にして、fpmの設定で9000番ポートをlistenするように修正しています。
/etc/php/fpm/pool.d/www.conf
listen = 9000
また普通にnginxをインストールするとバージョンが1.4とかになるので最新のstableをとってくるように変更しました
- name: get nginx signing key apt_key: url=http://nginx.org/keys/nginx_signing.key - name: update nginx sources1 apt_repository: repo="deb http://nginx.org/packages/ubuntu/ trusty nginx" - name: update nginx sources2 apt_repository: repo="deb-src http://nginx.org/packages/ubuntu/ trusty nginx" - name: install nginx apt: pkg=nginx state=present update_cache=yes
FuelPHPのインストーラの中身
FuelPHPのインストール時のコマンドでなにをやっているのか調査
$ curl get.fuelphp.com/oil | sh $ oil create fuelphp
$ curl get.fuelphp.com/oil | sh では、/usr/bin/oilにhttp://get.fuelphp.com/installer.shを書き込んでる
#!/bin/bash PREFIX="/usr/bin/" install_oil() { if [ `which sudo` ]; then sudo sh -c "curl --silent http://get.fuelphp.com/installer.sh > ${PREFIX}oil" sudo chmod +x ${PREFIX}oil else sh -c "curl --silent http://get.fuelphp.com/installer.sh > ${PREFIX}oil" chmod +x ${PREFIX}oil fi } # # Handle execution # main() { # Start installation install_oil exit 0 } main
http://get.fuelphp.com/installer.shの中身はこのようになっていて oil createが実行されたときにFuelPHPのgitリポジトリからcloneしてきてsubmodule updateしてcomposerをアップデートして、refine installでパーミッションを変更するといういたって単純なことをしてる。
#!/bin/bash # if we have the oil script in the current directory, start that if [ -f "./oil" ]; then php oil "$@" else # check for bash commandline options if [ "$1" == "create" ]; then # make sure git is installed if [ ! `which git` ]; then echo "For this installer to work you'll need to install Git." echo ' http://git-scm.com/' fi # clone the repository, and make sure the latest master is active git clone --recursive git://github.com/fuel/fuel.git "./$2" cd ./$2 branch=`git branch -a | grep -v "remote" | grep "master" | tail -1` branch=${branch#* } git checkout $branch git submodule foreach git checkout $branch # run composer php composer.phar self-update php composer.phar update # fix potential rights issues cd .. php "./$2/oil" refine install else echo 'This is not a valid Fuel installation so Oil is a bit lost.' echo ' http://fuelphp.com/docs/installation/instructions.html' fi fi
ただgitリポジトリの最新のmasterブランチをとってくるようになっているので、 バージョンを指定したいとかは使えないですが、常に最新使うんだったら特に問題なさげ
Ruby on Railsの開発環境構築
Using Vagrant for Rails Development - GoRails のを参考に実施
VagrantとVirtualBoxをインストール
Downloads – Oracle VM VirtualBox
$ vagrant plugin install vagrant-vbguest $ vagrant plugin install vagrant-librarian-chef
$ cd MY_RAILS_PROJECT # Change this to your Rails project directory $ vagrant init $ touch Cheffile
site "http://community.opscode.com/api/v1" cookbook 'apt' cookbook 'build-essential' cookbook 'mysql' cookbook 'ruby_build' cookbook 'nodejs', git: 'https://github.com/mdxp/nodejs-cookbook' cookbook 'rbenv', git: 'https://github.com/fnichol/chef-rbenv' cookbook 'vim'
# -*- mode: ruby -*- # vi: set ft=ruby : VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| # Use Ubuntu 14.04 Trusty Tahr 64-bit as our operating system config.vm.box = "ubuntu/trusty64" # Configurate the virtual machine to use 2GB of RAM config.vm.provider :virtualbox do |vb| vb.customize ["modifyvm", :id, "--memory", "2048"] end # Forward the Rails server default port to the host config.vm.network :forwarded_port, guest: 3000, host: 3000 # Use Chef Solo to provision our virtual machine config.vm.provision :chef_solo do |chef| chef.cookbooks_path = ["cookbooks", "site-cookbooks"] chef.add_recipe "apt" chef.add_recipe "nodejs" chef.add_recipe "ruby_build" chef.add_recipe "rbenv::user" chef.add_recipe "rbenv::vagrant" chef.add_recipe "vim" chef.add_recipe "mysql::server" chef.add_recipe "mysql::client" # Install Ruby 2.1.2 and Bundler # Set an empty root password for MySQL to make things simple chef.json = { rbenv: { user_installs: [{ user: 'vagrant', rubies: ["2.1.2"], global: "2.1.2", gems: { "2.1.2" => [ { name: "bundler" }, { name: "rails", version: "4.1.6" } ] } }] }, mysql: { server_root_password: '' } } end end
以下エラー対応
==> default: could not find recipe server for cookbook mysql ==> default: ==> default: [2015-04-04T10:18:45+00:00] ERROR: Running exception handlers ==> default: [2015-04-04T10:18:45+00:00] ERROR: Exception handlers complete ==> default: [2015-04-04T10:18:45+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out ==> default: [2015-04-04T10:18:45+00:00] ERROR: could not find recipe server for cookbook mysql ==> default: [2015-04-04T10:18:46+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1) Chef never successfully completed! Any errors should be visible in the output above. Please fix your recipes so that they properly complete.
Cheffileを以下のように修正
cookbook 'mysql','5.6'
$ vagrant provision
==> default: [2015-04-04T10:48:42+00:00] INFO: Running queued delayed notifications before re-raising exception ==> default: [2015-04-04T10:48:42+00:00] ERROR: Running exception handlers ==> default: [2015-04-04T10:48:42+00:00] ERROR: Exception handlers complete ==> default: [2015-04-04T10:48:42+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out ==> default: [2015-04-04T10:48:42+00:00] ERROR: rbenv_gem[2.1.2::bundler (vagrant)] (vagrant) (rbenv::user line 63) had an error: NoMethodError: undefined method `rbenv_rehash' for #<Chef::Provider::Package::RbenvRubygems:0x00000003e77ec8> ==> default: [2015-04-04T10:48:43+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1) Chef never successfully completed! Any errors should be visible in the output above. Please fix your recipes so that they properly complete.
cookbooks/rbenv/libraries/chef_provider_package_rbenvrubygems.rbを
def rehash rbenv_rehash new_resource do root_path rbenv_root user rbenv_user if rbenv_user action :nothing end.run_action(:run) end
以下に編集
def rehash e = ::Chef::Resource::RbenvRehash.new(new_resource, @run_context) e.root_path rbenv_root e.user rbenv_user if rbenv_user e.action :nothing e.run_action(:run) end
$ vagrant provision
以上でできたわけですが、chef-rbenvのリポジトリは一年以上放置されていてるようだったので、forkして修正を取り込みました
cocos2d-js 3.xでJS_CallFunctionでcrash
cocos2d-jsを2系から3系に変更した際にjavascriptのコールバックを呼ぶとexec bad accessでcrashするようになりました。
解決方法はこちらで JS_CallFunctionName crashed on iAP finished callback - Cocos2d-x Forum
JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET
をJS_CallFunctionする前に呼べばよいです。
all asynchronous function call have this problem
と書いてあったのでC++から非同期でjavascriptをたたく場合にはJSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJECTをつかう必要がありそう
Objective-Cでnil結合演算子的なことをするには
Swiftだとnil結合演算子として"??"が用意されてますが、 Objective-C(というかC)でも似たようなことができます。
ここ(Conditionals - Using the GNU Compiler Collection (GCC))に書いてあるように3項演算子の真ん中を省略すると、条件式が非0の場合に条件式の値になるので
x ? x : y
と
x ?: y
は同じになります。(厳密にはxの評価される回数が異なりますが)
なのでObjective-C的にnilだったらデフォルト値をつっこむみたいな処理を書くと
NSString* outputString = (inputString != nil) ? inputString : @"default";
を
NSString* outputString = inputString ?: @"default";
このように短く書くことができます