Xcode7のCore AnimationのProfilerはiOS9には対応していない
訂正:Xcode7.1では治ってました
Xcode7を使って、InstrumentsのCore Animationを使って実機でパフォーマンスを測ろうとしても下の図のように実機を選択することができません
iOS8.4の端末を使って試してみたところ選択することができたので、iOS9にはまだ対応していないと思われます
SwiftでUnitTest時に環境変数で分岐させる
テスト対象を@testable import XXXXを使ってimportした場合に、テスト対象のコードをテスト時だけ分岐させて特定の処理を行わないようにしたりしたい場合の対処方法です。
方法としては、環境変数がセットされているかどうかでテストかどうかを判定します。
環境変数の追加
Edit Scheme→Test→Argumentsを選択すると以下の様な画面になります。
ここのEnvironment Variablesに任意の環境変数を定義します。
分岐処理の追加
上記で追加した環境変数は、NSProcessInfo().environmentを利用して取得することができます。
例えば環境変数が設定されているときはreturnするみたいな場合は以下のようにすればよいです。
guard NSProcessInfo().environment["環境変数名"] == nil else { return true } // テスト時は実行しないコード ...
dynamic frameworkを使ってるプロジェクトでコマンドラインでipaファイル作成
Swiftでサブプロジェクトのモジュールをdynamic frameworkで取り込む場合にコマンドラインでipaファイルを作成する手順です。
環境:Xcode7.1
従来のやりかたではできなかったのでメモしておきます
発生した問題
以前Objective-Cでstatic libraryでやっていたときは以下のようなやり方で作成できていました
$ xcodebuild -sdk iphoneos -target XXX -configuration Release clean build PROVISIONING_PROFILE=xxxxx-xxxx-xxxxxx-xxxxx $ xcrun -sdk iphoneos XXX build/Release-iphoneos/***.app -o build/***.ipa --embed ****.mobileprovision
が、xcodebuildを実行したところ以下のように、dynamic frameworkをビルドする際にcode sign errorが発生してしまいビルドがこけてしまいます。
$ xcodebuild -sdk iphoneos -target XXX -configuration Release clean build PROVISIONING_PROFILE=xxxxx-xxxx-xxxxxx-xxxxx Build settings from command line: PROVISIONING_PROFILE = xxxxx-xxxx-xxxxxx-xxxxx SDKROOT = iphoneos9.1 === CLEAN TARGET YyyYYYYY OF PROJECT YyyYYYYY WITH CONFIGURATION Release === Check dependencies [BEROR]Code Sign error: Provisioning profile does not match bundle identifier: The provisioning profile specified in your build settings (“xxxxxx”) has an AppID of “jp.xxxx.xxxxx” which does not match your bundle identifier “com.example.Yyyy”.
解決方法
xcocebuild build でprovisioning profileを指定するとdynamic frameworkもそのprovisioning profileを使ってビルドしようとしてcode sign errorが発生してしまうので、xcodebuild archiveをつかって一旦xcarchiveを生成して、ipaにexportする際にprovisioning profileを指定してやることによって解決しました。
$ xcodebuild -scheme XXXX archive -archivePath release/xxx.xcarchive $ xcodebuild -exportArchive -archivePath release/xxx.xcarchive -exportOptionsPlist yyy.plist -exportPath zzz.ipa
exportOptionsPlistは以下のような感じになります。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>teamID</key> <string>MYTEAMID123</string> <key>method</key> <string>app-store</string> <key>uploadSymbols</key> <false/> </dict> </plist>
methodの値を変更することで、app store向けやadhocなどビルド方法を切り替えられます。
* app-store
* ad-hoc
* package
* enterprise
* development
* developer-id
その他のexportOptionsPlist内で設定できる内容は↓で確認できます
$ xcodebuild -h
CHANGELOGの自動生成
リリースごとにCHANGELOGをもれなく書くのは結構な手間です。 そこでcommit時にコミットログとしてCHANGELOGを記載しておき、リリース時にそれをまとめて出力することでその手間を削減します。
conventional-changelogを使ってCHANGELOG.mdを生成する方法について記載します。
事前準備
Node.jsのインストール(詳細はここでは割愛)
conventional-changelogのインストール
$ sudo npm install -g conventional-changelog
プロジェクトの設定
package.jsonというファイルをプロジェクトのトップディレクトリに配置します。 そして、以下のように、package.jsonにname, version, respsitoryを記載します。
- package.json
{ "name": "SampleApp", "version": "1.2.0", "repository": { "type": "git", "url": "https://github.com/xxxx/xxxx" } }
CHANGELOG情報のコミット
commitする際にCHANGELOGにのせる場合は、指定のフォーマットでcommitログ記載します。
angular, atomなど、プロジェクトによって独自のフォーマットが使われていて好みのものを利用します。
詳細はこちらを参照してください。 https://github.com/ajoslin/conventional-changelog/tree/master/conventions
CHANGELOGの生成
package.jsonを置いたのディレクトリ上で以下のコマンドを実行します。 (eslintのところはフォーマットによって変わります)
$ conventional-changelog -p eslint -i CHANGELOG.md -w
そうするとCHANGELOG.mdが生成されます。
ワークフロー
その後は、以下のようなワークフローでCHANGELOGを生成します。
Swift2 ドキュメントコメント
ドキュメントコメントを記載することで、Option + クリックでメソッドなどの説明を表示したりするようにすることができます。
ドキュメントコメントの指定方法は2通りあり、複数行の場合は/* ... /で囲い、一行の場合は///で始めます。
/** say message */ func say(message: String) -> String {
/// say message func say(message: String) -> String {
関数の説明
テキストをそのままかけば関数の説明として扱われます
/// say message func say(message: String) -> String {
引数の説明
引数一個の場合
/// say message /// - parameter message: a message what you want to say func say(message: String) -> String {
引数複数の場合
/// say message /// - parameters: /// - message: a message what you want to say /// - language: Japanese or English func say(message: String, language: String) -> String {
戻り値の説明
/// say message /// - returns: a message what did say func say(message: String) -> String {
エラーの説明
/// say message /// - throws: ServerError func say(message: String) throws -> String {
その他の記法
Markdown記法を使うことができます
/// # Title /// /// ## List /// - a /// - b /// - c /// /// ## Number List /// 1. list1 /// 2. list1 /// 3. list1 /// /// --- /// /// ## Reference /// [Link](http://github.com)
UnitTestでNSUserDefaultsに保存したデータを消す方法
UnitTestだとremoveObjectForKeyや
NSUserDefaults.standardUserDefaults().removeObjectForKey("key")
resetStandartUserDefaultsを呼んでもデータが消えません
NSUserDefaults.resetStandardUserDefaults()
解決方法
setObjectでnilを突っ込む
NSUserDefaults.standardUserDefaults().setObject(nil, forKey: "key")
Xcode7でシミュレーターのOSバージョンが表示されない不具合解消方法
Xcode7を使っていたところ、下の図のようにシミュレーター一覧を見ると、OSのバージョンが表示されず、どのOSなのかさっぱりわからなくなりました。。
解決方法
$ rm -rf ~/Library/Developer/CoreSimulator/Devices
そしてmacを再起動します
Swift Bond v4
Swift BondがSwift2.0に対応し、更にインターフェースが大きくかわりv4としてmasterに統合されました。
主な変更点としては、
クラス名を刷新
- これまではBond、Dynamicといった意味不明なクラス名がObservable, EventProducerに変わりわかりやすくなりました。
オペレーターの非推奨・廃止
- ->>や<<-といったオペレータの利用が非推奨となり、bind onlyの->|が廃止されました
と言った風に、いままで個人的に微妙だなと思っていた独自オペレーターや、謎のクラス名が改善されてよくなったなと思ってます。
基本的な使い方
値の変化の監視
以下のようなusernameの値の変化を検知したい場合、
var username: String = "Steve"
以下のようにObservable
class LoginViewModel { let username = Observable<String?>("Steve") }
そして値の変化のイベントを受け取る場合は、上記で定義したObservable
class LoginViewController: UIViewController { let viewModel = LoginViewModel() override func viewDidLoad() { viewModel.username.observe { event in print(event.value) } }
observe()は呼ぶとすぐに、引数で渡したコールバックが呼ばれます。 これを避けたい場合はobserveNew()を使えば登録時はコールバックが呼ばれません。
値の変化の監視とバインディング
監視対象とViewの要素をバインディングして、監視対象が変更されたらViewの要素を更新するといった場合、 bindTo()メソッドを使います。
class LoginViewController: UIViewController { @IBOutlet weak var usernameTextField: UITextField! let viewModel = LoginViewModel() override func viewDidLoad() { viewModel.username.bindTo(usernameTextField.bnd_text) }
UITextFieldのtextとバインディングする場合はbnd_textというObservableがBond側でUITextFieldのextensionとして用意されています。 この他にも基本的なViewは用意されているのでExtensions以下を見るか、bnd_で予測変換をだせば確認できます。
また、bindTo()もobserve()と同様に、登録時に即時バインディングが行われます。 これを避けたい場合はskip()を使います。
viewModel.username.skip(1).bindTo(usernameTextField.bnd_text)
skip(1)とやると最初はusernameの値がTextFieldには反映されず、次にusernameが変化したときから反映されるようになります。
双方向バインディング
bindTo()は監視対象→監視側への一方通行のバインディングでしたが、双方向にすることも可能です。
viewModel.username.bidirectionalBindTo(usernameTextField.bnd_text)
イベントのフィルタリング
特定の条件の変化があった際だけ、コールバックを受け取りたい場合は、fileterを使います。
filterはEventProducer型(Observableのsuper class)を返しますので、filterの戻り値をそのままチェーンしてやることでObservableと同じように扱えます。
@IBOutlet weak var loginButton: UIButton! let viewModel = LoginViewModel() override func viewDidLoad() { loginButton.bnd_controlEvent .filter { $0 == UIControlEvents.TouchUpInside } .observeNew { [unowned self] event in self.viewModel.login() } }
バインディングの解除
Observableが破棄されると自動的にそれに関わるバインディングは解消されるようになっていますので、普通に使っているぶんには解除を気にする必要はあまりありません。
解除方法
ObservableやbindToは戻り値としてDisposableTypeを返します。 DisposableTypeのdispose()メソッドを呼べば解除できます。
let disposer = viewModel.username.bindTo(usernameTextField.bnd_text) disposer.dispose()
Cellについて
普通はあまりバインディングの解除を気にする必要はありませんが、UITableViewCellなど再利用されるものについて使う場合は手動で解除してやる必要があります。(Cellが再利用されると解除しないと、他のバインディングが残ってしまうため)
やり方としては、cellにbindする際に、cellのbnd_bagにdisposeInしておき
let cell = (self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as? ListCellView)! let viewModel = dataSource[indexPath.section][indexPath.row] viewModel.name.bindTo(cell.nameLabel.bnd_text).disposeIn(cell.bnd_bag) return cell
prepareForReuse()でbnd_bagをdispose()します
override func prepareForReuse() { super.prepareForReuse() bnd_bag.dispose() }
Swift2でsubstring
Swift2(beta6以降)でadvance()が廃止され、BidirectionalIndexTypeなどにadvancedBy()が追加されたました。 書き方は以下の様に変わります
Swift1.2
var text = "123456" text = text.substringFromIndex(advance(text.startIndex, 3)) // "456"
Swift2
var text = "123456" text = text.substringFromIndex(text.startIndex.advancedBy(3)) // "456"
Swift2でArrayにArrayをinsertする
これまではsplice:atIndexでしたが
items.splice(insertItems, atIndex: items.endIndex)
Swift2(beta6以降)からinsertContentsOf:atに変わりました
items.insertContentsOf(insertItems, at: 1)
例
var items = ["a", "b", "c"] let insertItems = ["1", "2"] items.insertContentsOf(insertItems, at: 1) // ["a", "1", "2", "b", "c"]
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/