2015年12月17日木曜日

画面遷移時のトランジションエフェクトで変なメッセージ出る

別画面をトランジションエフェクト付きで開こうと、次のコードを実行したら、毎度変なメッセージが出ちゃうですよ。
一応トランジションは実施される。(遷移前のviewの透明部分に先に遷移後のviewの画像が出ちゃうとか細かい不具合は見られるけど)

alienVC.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
self.presentViewController(alienVC , animated: true, completion: nil)

遷移先の画面で
self.dismissViewControllerAnimated(true, completion: nil)
ってやって前の画面に戻る時にも同じメッセージが。

毎度出るメッセージ<_UIFlippingLayer: 0x14cf13c90> - changing property magnificationFilter in transform-only layer, will have no effect

和訳すると、
「トランスフォーム専用のレイヤーで拡大フィルターのプロパティーを変更しました。エフェクトできません」
とかそんな感じか?

これ、上みたいにコードで開く場合だけじゃなく、Segueで開く/戻る時にも出るんだよね。
UIFlippingLayerについてのメッセージだから、FlipHorizontalのエフェクトが関係するんだろうね?
UIModalTransitionStyle.FlipHorizontal.CrossDissolveにすると出ない。
海外のサイト見たら、CATransformLayerでも同様のメッセージが出てるようで、「無視することがオススメ」なんて回答されてることも。
うざいっちゃうざいけど、よくわかんないし、大きな実害も出てないから当面無視するしかないか。

2015年11月19日木曜日

NSUserDefaultsにenumを保存

変数currentStageは、下のように定義されてるenum(列挙型)の値が入ってる。
これをUserDefaultsに保存しようと思ったのだが、直接保存できないようなので調べたよ。

enum stage:String {
    case forest
    case beach
    case room
    case wheatField

}


まずそのまま保存するのはどうにもこうにも無理らしいので、扱いやすいrawValueを以下のように設定してやる。
ここではStringにしてる(使い方によってはIntでもいい)。
そのrawValueをString値(または数値)として保存するわけだ。

enum stage:String {
    case forest = "forest"
    case beach = "beach"
    case room = "room"
    case wheatField = "wheatField"

}

保存時
    func saveData() {
        let ud = NSUserDefaults.standardUserDefaults()
        ud.setObject(currentStage.rawValue, forKey: "currentStage")
    }


読み込んだら今度はそれをenum型に戻すんだけど、これがだいぶ悩んだ。
enum型にはメソッドがあって、rawValueを引数にしてやればいいのだな。
んで、引数の型をas! Stringとして強制的にStringに変換して。
さらにXcode様の指示どおり、最後にも!を付けたよ。
多分これでうまくいってる。

読み込み時
    func loadData() {
        let ud = NSUserDefaults.standardUserDefaults()
        //Stringで保存されたcurrentStageを列挙型stageの値に戻す
        currentStage = stage(rawValue: ud.objectForKey("currentStage") as! String)!
    }


もう21世紀なんだから、言語内で扱える型は全て直接読み書きできるようにすべきだと思うんだけど、どうだろうか?
いちいちこんなことで頭悩ますなんてアホらしいよね。


なお、UserDefaultsにあらかじめデータが入っていない状態で読み出そうとするとnilが返るため、きちんとチェックしておかないと最悪アプリが落ちることになる。
下記のようにif文のオプショナルバインディングというのを使うとコードが少し楽。
ud.objectForKey("currentStage")の結果がnilじゃなかったら値をtestに代入し、nilだった場合はtestにはfalseが入るためにelse文が実行されるというSwiftの仕組み。

        if let test = ud.objectForKey("currentStage") {
            //Stringで保存されたcurrentStageを列挙型stageの値に戻す
            currentStage = stage(rawValue: test as! String)!
            print("保存済み")
        } else {
            print("保存されてない")
        }

Xcode 7でfailed to code signと出た

Xcode 7で、iPadで実機確認しようとしたら、
「failed to code sign」
というダイアログが出てコンパイルできなかった。

なんか
No valid signing identities (i.e. certificate and private key pair) matching the team ID “XXXXXXXX” were found.

Xcode can attempt to fix this issue.
This will reset your code signing and provisioning settings to recommended values and resolve issues with signing identities and provisioning profiles.

The selected team's agent, '俺の名前' must agree to the latest Program License Agreement.  Please visit the Member Center.
https://developer.apple.com/membercenter
とかも出て、要するに自分のteamIDが認証できないらしい。

Fix itとかボタンがあったんで、その通りに押してもダメ。

最後のメッセージを読むに、プログラムライセンスの改定があったから、ブラウザからデベロッパーセンターにアクセスして、agreeしろということのようだ。

そういえば数日前に「Updates for apps on iTunes Connect.」とかいうメールが来てたっけな。
それと関係あるんだろうか。まあいいや。

早速agreeしたが、すぐにはXcodeには反映されないようで、お仲間がいるかどうかググって5分くらいしてから再度試したらうまくいったよ。(直接のお仲間はいなかったみたい)
その際、たしかデベロッパー登録してあるパスワードを入力致しました。

2015年11月16日月曜日

UIButtonの画像のアニメーション

UIImageViewに表示するUIImageはパラパラアニメができるけど、UIButtonに表示する画像もアニメーションができる。
これで、点滅するボタンとかが作れるわけだ。

UIButtonに画像を表示する機能はUIImageViewを使ってるようで、UIButtonのimageViewプロパティにアクセスする。
このプロパティ、リファレンスを読むとreadOnlyってなってるんだけど、さらにその配下にあるanimationImagesなどのプロパティには値をセットすることができる。

やり方はUIImageViewと一緒で、アニメのコマを入れた配列を用意し、animationImagesに設定。
Duration(時間)やRepeatCount(繰り返し回数。0だと無限繰り返し)を設定し、startAnimation()関数で開始する。

唯一注意点はUIButtonのsetImage()関数で、UIButtonに画像を表示する設定にしてやることだ。
通常の、文字だけ表示する設定じゃダメってことね。

        let imageArray:[UIImage] = [
            UIImage(named: "image01")!,
            UIImage(named: "image02")!
        ]
        animeBtn.setImage(imageArray[0], forState: .Normal) //これ必要
        animeBtn.imageView?.animationImages = imageArray
        animeBtn.imageView?.animationDuration = 1.0
        animeBtn.imageView?.animationRepeatCount = 0

        animeBtn.imageView?.startAnimating()

2015年11月3日火曜日

通知センターに通知を表示する

メールを受信した時や、スケジューラの予定時刻が来た時などにお知らせを出すiOSの通知センターに、アプリから通知を表示する方法。

AppDelegate.swiftにあるapplication()関数に以下のようなコードを書く。

    //AppDelegate.swiftの起動時に呼ばれる関数
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        //現在の通知を削除
        UIApplication.sharedApplication().cancelAllLocalNotifications();
        
        //通知登録前のおまじない
        //これがないとpermissionエラーが発生する
        application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes:
            [UIUserNotificationType.Sound,
            UIUserNotificationType.Alert,
            UIUserNotificationType.Badge],
            categories: nil))
        
        //通知の設定
        let notification:UILocalNotification = UILocalNotification()
        notification.fireDate = NSDate(timeIntervalSinceNow: 10)
        notification.timeZone = NSTimeZone.defaultTimeZone()
        notification.alertBody = "10秒たちました"
        notification.alertAction = "はい"
        notification.soundName = UILocalNotificationDefaultSoundName
        
        //通知登録
        UIApplication.sharedApplication().scheduleLocalNotification(notification)

        return true

    }

通知の設定以下〜return trueの前までは、べつにAppDelegate内に書かなくてもいいので、普通のViewController.swiftなどのしかるべきところに書いてもいい。
「アプリを切り替えてアクティブじゃなくなったら10秒後に通知」とかもできる。
上記のコードでは音を鳴らすようにしているが、soundNameをコメントアウトすれば鳴らない。

通知のダイアログ(iOSの個々の設定次第で出方が変わる)をタップすると、勝手に自分のアプリをアクティブにしてくれる。

通知をタップした時の処理

単にアプリをアクティブにしたのではなく、「通知をタップしてアクティブになった時」に処理をする場合は以下の関数で。
引数notification.alertActionで、上で設定したalertAction(はい)が取り出せる。
(使用不推奨のUIAlertViewを使ってるのはAppDelegate.swift内ではUIAlertControllerだとうまくいかなかったから。ま、気にすんな)

    //LocalNotificationがタップされた時に実行される
    func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
        let alert = UIAlertView();
        alert.title = "受け取りました";
        alert.message = notification.alertBody;
        alert.addButtonWithTitle(notification.alertAction!);
        alert.show();
    }

2015年11月2日月曜日

アプリをバックグランドにした時などに処理を行う

使用中のアプリを別のアプリに切り替えた時、また別のアプリから戻ってきた時に特定の関数を呼び出す方法。

NSNotificationCenterクラスを使うようだ。
引数nameにUIApplication〜と指定することでどういうタイミングでselectorの関数をコールするか選択する。

別のアプリから戻ってきた時(アプリ起動時にも)
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "forActive",
            name: UIApplicationDidBecomeActiveNotification,
            object: nil)

別のアプリに切り替えた時        
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "forBackground",
            name: UIApplicationDidEnterBackgroundNotification,
            object: nil)

以下のような値が用意されている。
調べたらもっとあるようだが、だいたい以下のもので事足りるのではないか?
アプリがどうかなる直前、直後などの細かい状態の変化でいろいろ重複して呼ばれるのはViewDidLoadやViewWillAppear関数なんかと一緒ね。

  • アプリがアクティブになった時/別のアプリから戻ってきた時
    • UIApplicationDidBecomeActiveNotification
  • アプリがバックグランドになる時
    • UIApplicationDidEnterBackgroundNotification
  • アプリ起動直後
    • UIApplicationDidFinishLaunchingNotification
  • アプリがアクティブになる直前
    • UIApplicationWillEnterForegroundNotification
  • アプリがアクティブでなくなる直前
    • UIApplicationWillResignActiveNotification
  • アプリ終了直前
    • UIApplicationWillTerminateNotification
  • デバイスの向きが変わる直前
    • UIApplicationWillChangeStatusBarOrientationNotification
  • デバイスの向きが変わった直後
    • UIApplicationDidChangeStatusBarOrientationNotification
参考:
Objective-C:NSNotificationCenterでアプリ起動・終了時にメソッドを呼び出す

2015年10月25日日曜日

UIImageViewの上下左右反転

imgというUIImageViewをいじる場合、imgのtransformプロパティをアフィン変換のメソッドでいじってやればよろし。
以下の例は横軸の値をマイナスにすることで左右反転させている。
縦軸もマイナスにすれば上下左右に反転される。

img.transform = CGAffineTransformScale(img.transform, -1, 1)

なお、マイナスの数値は「現在表示されている状態を反転させる」という意味なので、反転前の状態に戻す時はもう一度マイナスの数値を与える。
プラスの数値を与えると正しい画像の向きに戻るというわけではない。
現在画像がどの向きで表示されているのかを、コードの中で把握しておく必要があるわけやね。

2015年10月19日月曜日

UIViewのアニメーション方法いろいろ

種類があるようなので、気づいたら追記する形でまとめていく。

UIView.beginAnimations()

beginAnimations()とcommitAnimations()ではさむタイプ
メソッド前にアニメ前の状態を設定しておき、Durationの秒数かけてbeginAnimationsとcommitAnimationsの間に書かれた(AnimationCurveやDurationは設定なので別として)状態にアニメーションする。
centerなどの位置や、alphaチャネルも変化できる。
print文はアニメが始まってすぐに表示される。

        image.center = CGPointMake(0, 0)

        UIView.beginAnimations("imageMove", context: nil)
        UIView.setAnimationCurve(UIViewAnimationCurve.Linear)
        UIView.setAnimationDuration(10.0)
        image.center = CGPointMake(-50, 100)
        UIView.commitAnimations()
        print("アニメ終わり")

UIView.animationWithDuration()

クロージャを使うタイプ
メソッド前にアニメ前の状態を設定しておき、Durationの秒数かけて最初のクロージャに書かれた状態にアニメーションする。
centerなどの位置や、alphaチャネルも変化できる。
print文はアニメが終了後に表示される。

        image.center = CGPointMake(0, 0)
        
        UIView.animateWithDuration(10.0,
            delay: 0,
            options: UIViewAnimationOptions.CurveLinear,
            animations: { () -> Void in
            self.image.center = CGPointMake(-50, 100)
            }) { (Bool) -> Void in
                print("アニメ終わり")

        }

UIImageViewのパラパラアニメ

各フレームのコマを配列に用意しておき、それをstartAnimating()で開始する。
RepeatCountは繰り返しの回数で、0の場合無限に繰り返し。
途中で終了する際はanimeImage.stopAnimating()。
終了後は先に設定しておいたUIImage(ここでは"normalImage")になる。
前述のUIViewのアニメーションと組み合わせ、パラパラアニメしながら位置や大きさを変化などもできる。

                //最初にアニメ終了後に表示するimageを設定しておく
                animeImage.image = UIImage(named: "normalImage")
                //アニメのコマ設定
                let imageArray:[UIImage] = [
                    UIImage(named: "animeImage1")!,
                    UIImage(named: "animeImage2")!,
                    UIImage(named: "animeImage3")!,
                    UIImage(named: "animeImage4")!
                ]
                animeImage.animationImages = imageArray
                animeImage.animationDuration = 2.0
                animeImage.animationRepeatCount = 1
                animeImage.startAnimating()

2015年10月16日金曜日

UIViewとかのトランジションアニメーション

画面上でImageViewとかをフェードイン/アウトとかさせる方法

その1:UIViewのアニメーション機能を使う

現在の状態からbeginAnimations()commitAnimations()の間に書かれた状態に変化させてくれるという、iOSではおなじみの方法。
最初にimageViewであるimage1のαチャネルの値(要するに透明度)を0(透明)にしておいて、setAnimationDurationで指定した時間(秒)をかけて、αチャネルの値を1(不透明)にする。
"fadeIn"はanimationIDということで、別の処理との識別に使うので、識別できればなんでもいいようだ。(この例では識別に使ってない)
setAnimationCurve()にアニメーションの進行速度の変化の指定もできる。ここでの.EaseOutはフェードインが速く始まりゆっくり終わる指定。

        image1.alpha = 0
        //フェードイン
        //アニメーションのタイプを指定
        UIView.beginAnimations("fadeIn", context: nil)
        //イージング指定
        UIView.setAnimationCurve(UIViewAnimationCurve.EaseOut)
        //アニメーション秒数を指定
        UIView.setAnimationDuration(2.0)
        //目標のアルファ値を指定
        image1.alpha = 1
        //アニメーション実行
        UIView.commitAnimations()

他にもsetAnimationDidStopSelector〜メソッドでアニメ実行終了後に呼ぶメソッドをSelectorとして指定できたりするようだ。
いろいろあるようなので、調べて使うよろし。

その2:QuartzCoreを使う

ついでに逆にフェードアウトも指定してみる。
その1のやり方でやってもいいんだけど、フェードアウトした直後にimage1をremoveFromSuperview()したところ、フェードアウトせずにさっさとimage1がremoveされてしまったので、別の方法を探したところでてきたのがこれ。
QuartzCoreFrameworkのインポートとかはプロジェクトの設定で必要なのかな。Objective-Cみたいにクラスごとにimportはいらないようだ。
beginAnimationsと違い、どこまでの範囲をアニメーションするとかはないようなので、必要であれば別メソッドに分けて実行させたほうがいいかも。さすがにメソッドの終わりで一区切りつくと思うので。

image1.alpha = 1
//フェードアウト
let transition = CATransition()
transition.duration = 2.0
transition.type = kCATransitionFade
view.layer.addAnimation(transition, forKey: nil)
image1.alpha = 0

なおこの場合、アニメーションが終わった後に
view.layer.removeAllAnimations()
としてアニメ設定を削除しておかないと、次回使う時に不具合を起こすことになるので注意だ。

その他のトランジションもパラメータを変えたりしてできるだろう。

2015年10月14日水曜日

Storyboardで作ったViewControllerをコードで表示

Storyboardで作ったViewContollerをSegueじゃなくコードから表示しようとして以下をやったんだけど、画面が真っ黒になってうまくいかない。
調べたところ、どうやらこれではStoryboardで作ったファイルと結びつかないSecondViewControllerクラスができるだけらしい。

    let vc = SecondViewController()
    self.presentViewController(vc, animated: true, completion: nil)


そこでこうしたらできた。
        let vc: SecondViewController = self.storyboard!.instantiateViewControllerWithIdentifier"SecondView" ) asSecondViewController
    self.presentViewController(vc, animated: true, completion: nil)

もしくは
    let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SecondView") as! SecondViewController
    self.presentViewController(vc, animated: true, completion: nil)

"SecondView"はStoryboardのIdentity InspectorのStoryboard IDにあらかじめ入力しておく。
下のコードで"Main"はStoryboardの名前。
通常Main.Storyboardって名前になってるので、拡張子を取ったMain。

これで晴れてStoryboardでグラフィカルなエディタで作ったViewControllerを表示できるのだね。
変数vcにはSecondViewControllerのプロパティやメソッドもあるので、それを操作してやることもできる。

むろん、画面上のインターフェース部品などをコードで全部書くつもりなら最初のやり方でもうまくいくと思う。

普段Segueばかり便利に使ってるから戸惑った。

Swiftでクラスのファイル名の変更

一度つけたクラス名のファイルを変更する場合、XcodeでProject Navigator(左側に出てるファイルの一覧が表示されてるアレだ)で当該のファイルを選び、File Inspector(通常右側に出てる、いろんな情報を表示してるあそこ)のIdentity and TypeのNameを変更してやればいい。

Objective-Cではコードの中に書かれたクラス名を右クリックしてRefactor > Rename...とかやるんだったと思った。
Swiftで同じことやると「RefactorはCとObjective-Cしか対応してないよ」って英文メッセージが出る。

2015年9月29日火曜日

UIViewAnimationとかのoptionsの指定が変わった

Objective-CやSwift 1.xでは、UIViewのanimationWithDurationメソッドとかではアニメーションの速度の変化などをoptionsという引数にバイナリーのビット演算で渡してた。

let opt = UIViewAnimationOptions.Autoreverse | UIViewAnimationOptions.CurveEaseIn

UIView.animateWithDuration(duration: 3.0, delay: 0, options: opt, animations: 以下略)

でもSwift 2.0から仕様が変わりやがりまして、[ ]内に , 区切りで並べるようになった。
let option: UIViewAnimationOptions = [UIViewAnimationOptions.Autoreverse, UIViewAnimationOptions.CurveEaseIn]

うっかり古い書き方で書くと、
Binary operator '|' cannot be applied to two 'UIViewAnimationOptions' operands
というエラーが出てしまうので注意だ。
いろいろ変わって面倒くさいね。

2015年9月21日月曜日

AnyObject型に入れたクラスのメソッドが実行できない?

Swiftには、Objective-Cのid型にあたる汎用型のAnyObject型というのがあるわけだ.

しかし、以下のようにClassAにmethodAというメソッドとaというプロパティが設定されていた場合、型推論でClassA型になった変数obj1についてはメソッドもプロパティもいじくれるが、
//うまくいく例
let obj1 = ClassA()
obj1.methodA()
obj1.a = 100


AnyObject型として定義した場合はメソッドもプロパティもいじれず、エラーになってしまう。
//うまくいかない例
let obj2: AnyObject = ClassA()
obj2.methodA()
obj2.a = 100

一つの変数にいくつものクラスを入れて使いまわしたいと思ったのだが、ダメなのかな?

ちなみにObjective-Cでも同じこと試してみたけどダメだった。
ただし、プロパティはSetter/Getterメソッド使えばアクセスできた。

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になるようだ。