2015年9月18日金曜日

iOS9とXcode7になって

9月17日、iOS9がリリースされたのでさっそくうちのiPad3にインストールした。
多少挙動が怪しいアプリもあるが、今のところ特に大きな問題は出ていない。
動作速度もiOS8.4に比べて遅くなったとは思えない。
目玉機能のSplit ViewやSlide Overといった機能は最近のiPad、iPhoneでしか使えないんだね。うちのでは使えなくて残念。

ま、そりゃいいのだが、自作アプリで動作がおかしい物を発見。

多言語の判断が変わった

多言語対応しているSkyReporterで、コード内で言語別にメッセージを変えている部分が誤動作して、英語のメッセージが出るようになってしまった。
これは言語環境を調べる関数でAPIが"ja"じゃなくて"ja-JP"とかを返すようになったためだという。
こんなとこいちいち変えるなよ! と思う。
iOS8以前にも対応するには文字列の先頭が"ja"かどうかで判断した方がいいようだ。
後でアップデートせねば。


Swiftの仕様が少し変わった模様

自作のSwiftプロジェクトをXcode7で開いたところ、「コードの書き方が変更になったからコンバートしちゃっていい?」ってメッセージが出た。
なんか、Swift 2.0にバージョンアップされたんだとか。
どこがどうコンバートされるのかは確認できる。
たとえばObjective-CのNSLogにあたるprintln()がprint()関数になった模様。

他にもコンバートできなかった箇所がエラーになったり。optionalの関係で、配列にしまうUIImageに!を付けてやったら通った。
optionalは今でもまだよくわかってないのに、また仕様が変わったら困るな。
そうでなくても手持ちの参考書と現在のSwiftの仕様が違ってきている。


ディレクトリが見つからないと出る

Xcodeで開いたら以下のWarningが出て、消えない。

Directory not found for option '-F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator9.0.sdk/Developer/Library/Frameworks'

iPhoneシミュレータのiOS9.0のSDKのディレクトリが見つかんねーぞ、こら、ってことなんだろうけど、俺何もしてないぞ。
プロジェクトの中のBuild Settings、「アプリ名Tests」の中のSerch PathsのFramework Serch Pathsの中を消せばいいようだ。
左側のコラムにPROJECTとTARGETSがあって、TARGETSの中にさらにアプリ名とアプリ名Testsというのがあり、それぞれにSearch PathsのFramework Search Pathsという設定があるので間違えないように。
文字で書くより以下の画像を見たほうがわかりやすかろう。

2015年9月4日金曜日

AlertControllerの書き方


SwiftでAlertを表示しようと思い、AlertControllerのコードを書いたのだが、表示されない。

ログには

whose view is not in the window hierarchy!

などというエラーメッセージが表示されている。

調べたら、viewDidLoad()中に書いていたせいで、viewDidAppear()中に書きなおしたら表示できた。
viewDidLoad()の段階ではviewの情報が読み込まれただけだから、画面上の部品の階層がはっきりせず、「そんな段階でさらにAlertなんて表示できるかい!」と言われたわけなのだな。
Objective-Cでやってた頃はうまくいったような気もするのだが、Swiftや最近のiOSで仕様が変わったのかもしれん。まあいいや、覚えておけば。


具体的な書き方は以下。

    override func viewDidAppear(animated: Bool) {
        self.showAlert(title: "タイトル", message: "・メッセージ", actionTitle: "確認")
    }
    
    //AlertControllerで表示
    func showAlert(#title: String, message: String, actionTitle: String)
    {
        let alertCtl = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) //UIAlertControllerStyle部分は省略可
//alertCtlにボタンごとの情報を追加する
        alertCtl.addAction(
            UIAlertAction(
                title: actionTitle,
                style: UIAlertActionStyle.Default, //デフォルトボタン
                handler: nil) //ボタン押下時の処理はhandler:にクロージャとして書く
        )
        
        alertCtl.addAction(
            UIAlertAction(
                title: "キャンセル",
                style: UIAlertActionStyle.Cancel, //Cancelボタンは一番下に出る
                handler: nil)
        )
        
        alertCtl.addAction(
            UIAlertAction(
                title: "削除",
                style: UIAlertActionStyle.Destructive, //Destructiveは赤字になる
                handler: nil)
        )
        
//実際のAlert表示処理
        presentViewController(alertCtl, animated: true, completion: { println("アラート表示しました")}) //頭にself.を付けても付けなくてもどっちでもいい
//表示後の処理はcompletion:にクロージャとして書く
    }



なおiPadで表示する場合、preferredStyle: が.Alertならいいが、.ActionSheetの場合は以下のpopoverPresentationContollerのプロパティを設定してやらないとアプリが落ちる。
ActionSheetはiPhoneなら下からニョキって出るだけだけど、iPadの場合は画面上のどこかから吹き出しの形で出るみたいね。(ほとんど使ったことない)
だからどこから吹き出すのかなんかを指定してやらんといかんようだ。
なお、iPhoneで表示する場合は無視されるので、事前にデバイスを判断するif文などは必要ない。

        //For ipad And Univarsal Device
        alertCtl.popoverPresentationController?.sourceView = self.view

        alertCtl.popoverPresentationController?.sourceRect = CGRect(x: (self.view.frame.width/2), y: self.view.frame.height, width: 0, height: 0)

2015年9月2日水曜日

ImageViewの画像がはみ出す

ImageViewに画像を表示させる場合、画面の縦横比が合わないと見にくいことになるので、ViewのModeで、Aspect FitとかAspect Fillとかを設定することが多いだろう。
Aspect FillにすればImageViewの縦横比と実際の画像の縦横比が違っても、画像の縦横比を崩さずに拡大して表示してくれる。

下の例だと、「会話」などのボタン表示スペースを空けて、その上までがImageView担っている。
(TextViewのテキストが途中でブチ切れてるのはXcodeの仕様なんで、イラっとしても今は気にしない)

期待する表示

ところがビルドしてみると画像がはみ出してボタンにかかってしまい、見づらくなってしまった。
Constraintsを調べても間違っているようには思えない。
期待に応えてくれなかった表示

調べたところ、ImageViewのインスペクタから、DrawingのClip Subviewsボタンにチェックを入れれば直った。

DrawingのClip SubviewsをON


デフォルトでは親View(この場合ImageView)に載ってる子View(この場合画像自体だろう)が親よりでかい場合、はみ出すよう(Clipしない)になっているのだな。
それでチェックをすれば親の大きさの範囲でClipしてくれるようになるわけだ。
めでたしめでたし。

Viewの上にButtonを貼ってあるとか親子関係がわかりやすいならともかく、ImageViewと画像まで親子関係を求められるとは思わんかった。
普通はImageViewの範囲でClipされるのをデフォルトにすべきだと思うんだが…。他の部品と合わせちゃったのかね。
期待どおり

2015年8月4日火曜日

ファイルを保存するディレクトリの取得

iOSで開発者が利用できるディレクトリーのパスは、アプリ名などから指定することができないようなので、用意されているメソッド/関数を使って取得する。

以下のメソッド/関数がそれ。
NSSearchPathForDirectoriesInDomains

第1引数は NSSearchPathDirectory、
第2引数は NSSearchPathDomainMask、
第3引数が BOOL
となる。

第1引数のNSSearchPathDirectoryはディレクトリの種類を定数で指定する。
定数はSwiftでは以下のように定義されている。
enum NSSearchPathDirectory : UInt { case ApplicationDirectory case DemoApplicationDirectory case DeveloperApplicationDirectory case AdminApplicationDirectory case LibraryDirectory case DeveloperDirectory case UserDirectory case DocumentationDirectory case DocumentDirectory
(後半略)
}

Objective-Cでも同様に以下のように。
enum { NSApplicationDirectory = 1, NSDemoApplicationDirectory, NSDeveloperApplicationDirectory, NSAdminApplicationDirectory, NSLibraryDirectory, NSDeveloperDirectory, NSUserDirectory, NSDocumentationDirectory, NSDocumentDirectory,
(後半略)
};
typedef NSUInteger NSSearchPathDirectory;

Objective-Cでは明らかに定数として最終的に数値が当てはめられているようだが、Swiftの場合、定義のリファレンスを見る限り、数値は出てこない。
まあでもUIntって型指定されてるんで、実体は数値なんだろう。

んで、返り値が配列で返されるので、Swiftでは以下のように書く。
.DocumentDirectoryてのはさっきの定数。
列挙型なので先頭に「.」を付けて必要なのを指定してやる。
var ary = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)

Objective-Cだと
NSDocumentDirectory
とか書くわけね。
Objective-Cだと、クラスなんだか定数なんだかわかりづらいから、Swiftの方がわかりやすいかもね。慣れるまでは「あれ?」ってなるかもしれないけど。


第2引数のNSSearchPathDomainMaskでDocumentDirectory中のさらに別なディレクトリを指定してるんだと思う。
.UserDomainMask ってやればユーザーが書き込めるディレクトリが指定できる。


第3引数はよくわからんが、チルダ「~」を扱うためのものらしい。true(Objective-CならYES)でいいようだ。

返り値が配列なのは、おそらく同様なディレクトリが複数あった時にすべてを返すためだと思うが、だいたいは1個しかないだろうから、
var path = ary[0]
としてindex 0の値を使ってやればpathになるようだ。

2015年7月7日火曜日

Spriteがタッチされたら処理を行う

やりたいこと

spriteをボタンとして扱いたい。

やり方

SKSpriteNodeはUIButton的な機能は持ってないので、touchBegan()の中でtouch箇所がボタン用スプライトの位置かを判定し、処理をすることになる。ちょっとめんどくせえ。
スプライトにはあらかじめ任意のnameを付けておけば複数のボタンに対処できる。

Swift4版

以下はボタン用スプライトをタッチしたらParticleを表示するもの。
ボタンにはあらかじめ"fireButton"というnameが設定されている。

//fireButtonがタッチされたらparticle処理して終了
if let touch = touches.first as UITouch? {
    let location = touch.location(in: self)
    //本とかだと.firstでやるように書かれてるが、途中から反応しなくなることがあるので、
    //.lastにしたほうがいい。原因は不明。これで支障ないのでこれでいい。
    if self.nodes(at: location).last?.name == "fireButton" {
        //パーティクル
        let particleSpark = SKEmitterNode(fileNamed: "SparkParticle.sks")
        particleSpark.position = CGPoint(x: 0, y: 0)
        particleSpark.numParticlesToEmit = 400
        self.scene?.addChild(particleSpark)
        
        return
    }

}

注意点

Particleをいっぱい表示させてたところ、途中からボタンが反応しなくなった。
参考書などでは
if self.nodes(at: location).first?.name == "fireButton" { }
となってたのだが、どうやら.firstの中身がParticleになってしまい、nameが付いていないのではじかれてしまったのだ。
これは.last?にすることで回避できたが、なぜ途中から変わっちゃうんだろう? Particleって勝手に消えてくれるはずだよね? 画面見てもわからないが、不可視要素としてボタンに引っかかってるんだろうか?

Swift2.1版(古いけど参考までに残しとく)

Spriteがタッチされたら何か反応示す場合、Spriteに名前を付けておけば、addChildしてある親SceneのtouchBeganメソッドから、nodeAtPointメソッドでタッチされたノードを得ることができる。
そしてそれの.nameを調べればどのSpriteがタッチされたか判断できる。

以下は、「powerGauge」という名前をつけたSpriteがタッチされたら、バカみたいなメッセージをprintlnするというもの。
touchedNode?.nameをprintltすると「Optional("powerGauge")」というメッセージがコンソールに出るが、if文で判定する際は「powerGauge」だけでいい。

    //タッチ検出
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch //Swift1.2から.firstに
        let location = touch.locationInNode(self)
        
        let str = selectSpriteForTouch(location)
        println("タッチ開始 \(str)")
    }
    
    
    override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch //Swift1.2から.firstに
        let location = touch.locationInNode(self)
        
        let str = selectSpriteForTouch(location)
        println("タッチ終了 \(str)")
    }
    
    override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
        let touch = touches.first as! UITouch
        let location = touch.locationInNode(self)
        
        if let touchedNode = self.nodeAtPoint(location) as? SKSpriteNode {
            touchedNode.position = location
        }
    }

    //タッチしたSpriteNodeのnameを得る
    func selectSpriteForTouch(location:CGPoint) -> String {
        let touchedNode = self.nodeAtPoint(location) as? SKSpriteNode
        println("タッチされたSprite名: \(touchedNode?.name)")
        let str:String = (touchedNode?.name ?? "にる")
        if touchedNode?.name == "powerGauge" {
            println("パワーゲージだよ~~~ん")
        }
        return str
    }

touchesMoved中の
if let toucheNode = self.nodeAtPoint(location) as? SKSpriteNode {
            toucheNode.position = location
        }
は、Swift独特の書き方で、右辺の値がnilじゃなかったらtouchedNodeに値を代入して { }内の処理を行うというもの。
値がnilだったらif文の { }が飛ばされるので、nilによる例外エラーとかが起きない仕組みだ。

selectSpriteForTouch中の
let str:String = (touchedNode?.name ?? "にる")
は、Swiftが原則的にnilを変数に入れられないため、touchedNode.nameがnilだった場合は「にる」をstrに入れよというデフォルト値の設定。

optional関係はまだ不慣れなため、もっといい書き方があると思うけど。

Spriteのカスタムクラスを作った場合でも、いちいちdelegateとか使わなくてもタッチされたSpriteを特定できるので、コードが簡単になる。
(delegate使ったほうが楽な場合もあると思うけどね)

2015年7月2日木曜日

SpriteのゲームはSpriteKitレベルエディタで作ろう

UIViewを使うアプリの画面デザインをするのには、InterfaceBuilderから続くエディタを使うけど、SpriteKitを使うアプリには最初そういうのがなかった(と思う)。
でもXcode6くらいからSpriteKitエディタが付いて、WYSIWYGなグラフィカルな画面デザインができるようになった。
(エディタの正式な名前がよくわからなかったのだが、SpriteKit Level Editorというらしい)

SpriteKitを使ってプロジェクトを作り、拡張子に.sksが付いてるファイルを開けばエディタが開く。
以下のはもう部品を配置したものだが、一般的なSprite(Color Sprite)、照明効果のLight、UILabelのSprite版、パーティクルのEmitterなど、ひと通りのものが右下からドラッグして配置できる。
デフォルトだと画面サイズがなぜか正方形になっちゃうんで(各種iPhone、iPad全てに対応させる概念からだと思うが、まだよくわからん)、必要であればインスペクタのSizeで変えてやるといい。

細かい使い方はUIView用のInterfaceBuilderに準じてるからわかるだろう。
コードからこのエディタで作った画面は、SpriteKitでプロジェクトを作った時のテンプレートとしてGameViewControllerに書かれてくるunarchiveFromFileメソッドで表示している。

実際のゲームの内容はGameSceneの中に書くのだが、エディタに配置したSpriteはInterfaceBuilderみたいにクラスにcontrol+ドラッグ(もしくは右ボタン+ドラッグ)でプロパティを作ることができないので、簡単なコードで認識させる。

あらかじめエディタの方で各SpriteにNameを付けておき、コードの中で以下のようにすればInterfaceBuilderのOutletに相当するものが得られる。
   let barLong = childNodeWithName("barLong")
   var powerGauge = childNodeWithName("powerGauge") as! SKSpriteNode
上のように単にOutlet接続しただけでは、クラスはSKNodeになるようだ。
そこで、SKSpriteNodeのいろんなメソッド、プロパティを使う際は下のように「as!」を使ってSKSpriteNodeにキャストしてやる。

このように、SKNodeのようなスーパークラスを派生クラスSKSpriteNodeにキャストすることを、ダウンキャストと言う。


まだ使い方でわかってないことも多いので、わかったら追記する。

SpriteKit Level Editorについて書かれたページ
[iOS 8] SpriteKit でミニゲームをつくる #5 画面パーツ配置
An iOS 8 Swift Sprite Kit Level Editor Game Tutorial

2015年7月1日水曜日

文字と数字の変換

数字文字列 → 数字


Objective-C

    NSString *str = @"100";
    int a = [str intValue];

Swift

    let str = "100"
    let a:Int = str.toInt()!


数字 → 文字列


Objective-C

    int a = 100;
    NSString *str = [NSString stringWithFormat:@"%d",a];

Swift

    let a = 100
    let str1 = "\(a)"
    let str2 = a.description
str1の方はObjective-CのstringWithFormat的な書き方。
str2の方がSwift的で楽だね。