MILLEN BOX

音楽好きの組み込みソフトエンジニアによるプログラミング(主にiOSアプリ開発)の勉強の記録

Navigation Barをカスタマイズ [UINavigationBar]

かるーくかるーくメモを残します。

本日はNavigationBarのカスタマイズの方法について。
Githubは以下です。

▶︎GitHub - anthrgrnwrld/customNavigationBar

1. Navigation Barをアニメーションを使って隠す

  • 隠す
self.navigationController!.setNavigationBarHidden(true, animated: true)
  • 表示する
self.navigationController!.setNavigationBarHidden(false, animated: true)

2. Navigation Barを透明にする

  • 透明化
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: .Default)
self.navigationController!.navigationBar.shadowImage = UIImage()
  • 透明化解除
self.navigationController!.navigationBar.setBackgroundImage(nil, forBarMetrics: .Default)
self.navigationController!.navigationBar.shadowImage = nil

3. Navigation BarのTInt Colorを変更する(例えば赤に)

self.navigationController!.navigationBar.tintColor = UIColor.redColor()

Unwind Segueのアニメーションをカスタマイズ [UIStoryboardSegue]

ブログの更新ができてない。
もうこうなったらつぶやくように更新していこうと思います。

本日はUnwind Segueのアニメーションをカスタマイズする方法をベタっとメモしておきます。

Githubは以下です。

▶︎GitHub - anthrgrnwrld/myUnwindSegue

1. Storyboard

通常使用する場合から得に変わったことはしません。
参考にキャプチャ画像を貼っておきます。

f:id:anthrgrnwrld:20160616080443p:plain

...いや、忘れてました。 UnwindSegueのクラスとして今回カスタマイズ用に定義したクラス名を指定する必要があります。
(今回は3で作成するmyUnwindSegueを指定)
f:id:anthrgrnwrld:20160616123921p:plain

2. ViewController (起動時に表示されるViewController)

コードは以下です。
得に通常と変わったことはありません。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func returnFirstView(segue: UIStoryboardSegue) {
        print("\(#function) is called!")        
    }


}

3. myUnwindSegue (UnwindSegueで使用するカスタムクラス)

新規ファイルを作成し、以下のようにUnwindSegue時に実行されるアニメーションを記載する。

import UIKit

class myUnwindSegue: UIStoryboardSegue {
    
    let flag = false
    
    override func perform() {
        
        /***** 準備 ******/
        //ViewControllerのインスタンスを取得する
        let firstVCView = destinationViewController.view as UIView!
        let secondVCView = sourceViewController.view as UIView!
        
        //画面の縦横を取得する
        let screenHeight = UIScreen.mainScreen().bounds.size.height
        let screenWidth = UIScreen.mainScreen().bounds.size.width
        
        //戻った先のビューを画面外に設置する。
        firstVCView.frame = CGRectMake(-screenWidth, 0.0, screenWidth, screenHeight)
        
        //戻った先のビューを現在の画面の上にのせる
        let window = UIApplication.sharedApplication().keyWindow
        window?.insertSubview(firstVCView, aboveSubview: secondVCView)
        
        
        /****** アニメーション *****/
        UIView.animateWithDuration(0.5, animations: { () -> Void in
            //現在のビューを画面外に移動させる。
            secondVCView.frame = CGRectOffset(secondVCView.frame, screenWidth, 0.0)
            //戻った先のビューを画面上に移動させる。
            firstVCView.frame = CGRectOffset(firstVCView.frame, screenWidth, 0.0)
            
        }) { (Finished) -> Void in
            //現在の画面を閉じる
            self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
        }
    }
    
}

以上です。

手軽にBlurViewが使えるSABlurImageViewを使ってみました [swift2.1] [SABlurImageView]

ものすごく久しぶりの投稿です。。。。
罪悪感でいっぱい。。。。

先日(と言っても随分前ですが、、、)iOS8で追加されたUIVisualEffectViewを使ってBlur効果を実現しました。

▶︎iOS8で追加されたUIVisualEffectViewを使ってBlur効果を作成する [UIVisualEffectView] [UIBlurEffect] - MILLEN BOX

しかしこの方法には問題があります。iOS端末の設定によってはBlurEffectがかかりません。以下記事参照。

▶︎UIVisualEffectViewを使ったblur効果が環境によって動かないぞ?と思ったら [UIVisualEffectView] [UIBlurEffect] - MILLEN BOX

そこで今回はiOS端末の設定に依存しないBlurRffectの掛け方をシェアしようと思います。

f:id:anthrgrnwrld:20160202204238g:plain

今回作成したプロジェクトのGitHubは以下です。

▶︎GitHub - anthrgrnwrld/saBlurEffect

どうやって実現する?

さてBlurEffectの実現方法ですが、実は先人の方々が既に作ってくれているのです。
その中で今回は、オープンソースとして公開されているSABlurImageViewというクラスを使用したいと思います。

▶︎GitHub - marty-suzuki/SABlurImageView: You can use blur effect and it's animation easily to call only two methods.

以下のQiitaにも記事がありました。

▶︎iOSアプリ開発でブラーエフェクトを手間なく使う方法 - Qiita

このクラスを使ったサンプルも上記リンクにはあったのですが、方法がちょっと私がしたい使い方ではありませんでした。
サンプルの方法だとUIImageView全体にBlurEffectをかけるようなイメージで書かれていました。
しかし、私はこれのように、UIImageViewの特定部分のみEffectをかけるような形で使用したかったのです。

そこで「特定部分のみEffectをかける」ということを実現するために、UIImageやUIViewから特定部分をUIImageとして切り出す方法を学び、以下の記事達を書きました。

▶︎UIImageの一部を切り取る方法 [swift2.1] - MILLEN BOX

▶︎UIViewの一部をUIImageとして切り取る方法 [swift2.1] [Context] [CGAffineTransform] - MILLEN BOX

そして今日、晴れて(やっと)SABlurImageViewのオレオレな使い方を公開する運びとなりました。

SABlurImageViewを使用する準備

SABlurImageView をダウンロードします。
ダウンロード後生成された"SABlurImageView"フォルダ直下にある"SABlurImageView"フォルダ(同じ名前だから注意)を、自分のプロジェクトにガツンと放り込みます。
これで使用準備は完了です。

f:id:anthrgrnwrld:20160202075931p:plain
※ testSABlurImageViewが今回私が作成したプロジェクト名、そしてSABlurImageViewフォルダが上記手順にて追加したものです。

ちなみにCocoaPodsでもSABlurImageViewは使用出来ますが、私の宗教上の理由により今回は上記方法にて利用しています。(単に覚えるのが面倒くさいだけ。。。)

SABlurImageViewの使用方法

単にUIImage全体にBlurEffectをかけるだけであれば、引数にUIImageを渡してあげてSABlurViewをインスタンス化し、addSubViewすれば行けます。

effectView = SABlurImageView(image: effectImage)
effectView.addBlurEffect(25, times: 2)    //Blur度合とかける回数を指定
self.addSubView(effectView)

しかし今回私は「渡したUIImage」の「特定範囲にのみ」Effectをかけたかった...ということで、以下の addVirtualEffectView という関数を作成しました。
ポイントはev.frame = frameRectにてblurをかける大きさを指定しているところでしょうか。
(追記)またeffectViewについてはViewControllerのプロパティとして private var effectView : SABlurImageView? //Effect用View という形で予め宣言済みです。(Github参照)

    /**
     エフェクトを適用する
     
     - parameter view:blurをかける対象のView
     - parameter rect:blurをかける位置とサイズ(CGRect)
     - parameter blurLevel:blurレベル
    */
    func addVirtualEffectView(view :UIView?, rect :CGRect?, blurLevel :SABlurEffectStyle?) {
        //print("\(__FUNCTION__) is called!")
        
        //effectViewの内容を一旦初期化する
        initEffectView()
        
        guard let blrlv = blurLevel else {
            return
        }
        
        guard let targetView = view else {
            return
        }
        
        guard let frameRect = rect else {
            return
        }
        
        //blurをかけたい部分をUIImageとして取り出す(=effectImage)
        let effectImage = clipView(view, rect: frameRect)

        
        //blurをかけたい部分(=effectImage)をBlur生成用関数(=SABlurImageView)にセットする
        effectView = SABlurImageView(image: effectImage)
        
        guard let ev = effectView else {
            print("\(__LINE__) \(__FUNCTION__) is called!")
            return
        }

        //Blurの大きさ,座標,形を指定する
        ev.frame = frameRect
        ev.layer.masksToBounds = true
        ev.layer.cornerRadius = 20.0
        
        //blurのかけ具合を指定する
        switch blrlv {
        case .Weak:
            ev.addBlurEffect(25, times: 2)
        case .Default:
            ev.addBlurEffect(50, times: 2)
        case .Strong:
            ev.addBlurEffect(100, times: 3)
        }
        
        //blurがかかったViewを追加する
        targetView.addSubview(ev)
        
    }

あとはこれとかこれとかこれとかでやったことのおさらいで実装の意味は理解できると思います。
更新、ちょっと空くと億劫になるのと、文章の書き方忘れてしまいますね。。。
もっとサクッと更新できるようにしていきたいです。

アプリが非アクティブになったことをViewControllerで検知する方法 [UIApplicationDelegate]

アプリがアクティブでなくなったことをViewControllerにて検知する方法について調べてみました。

自分ポイント1 - 非アクティブ時に実行されるメソッドについて

アプリが非アクティブになった時、AppDelegateの applicationWillResignActive が実行されます。
アプリが非アクティブになった時にやりたい処理は、このメソッドの内部に記載すればOKです。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    ...

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.

        //ここに非アクティブ時にやりたい処理を書く

    }

    ...

}

上記方法で「非アクティブになった時」にさせたい処理を書くこと原理上できますが、例えばその際にViewController内で宣言されたプロパティの値を変更したい場合はどうでしょうか。
そのプロパティはViewController内でのみ変更可能なため、上記方法では不可能です。
じゃあ、どのようにすれば??

自分ポイント2 - 非アクティブのViewControllerでの検知の仕方について

実はAppDelegateの親クラスUIApplicationDelegateのメソッドが実行される際、通知信号が出力されます。
今回はapplicationWillResignActiveが実行された際に出力される通知信号をViewControllerで受け取ってあげるように準備してあげれば、ViewControllerにて非アクティブを検知できます。
通知信号の受信の準備ですが、以下の順番でViewControllerにコードを追記していくと分かりやすいです。

  1. 通知信号を受け取った時に行うメソッドを、(処理内容の記載は空っぽで良いので)まず書いておきます。(rcvSignalOfNonActiveとします)
  2. ViewDidLoad辺りに以下のコードを追記します。このコードが「通知信号を受けとる準備」になります。
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "rcvSignalOfNonActive", name:UIApplicationWillResignActiveNotification, object: nil) 引数selectorには手順1で追加した通知信号を受け取った時に行うメソッドを書きます。
    引数nameに指定されているUIApplicationWillResignActiveNotificationは、applicationWillResignActiveが実行された際に出力される通知信号の名前です。

  3. 通知信号を受け取った時に行うメソッドrcvSignalOfNonActiveの内部処理を書きます。
    今回は特に何がしたいということはありませんので、print("\(__FUNCTION__) is called!")を書いて呼び出されたことを分かるようにしておきます。

  4. おっと忘れていました。
    ViewControllerが継承するクラスとしてUIApplicationDelegateを追記しておいてあげて下さい。

以下にコードを書いておきます。

import UIKit

class ViewController: UIViewController, UIApplicationDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "rcvSignalOfNonActive", name: UIApplicationWillResignActiveNotification, object: nil)  //UIApplicationWillResignActiveNotificationを受信した時のコールバック関数を登録
                
    }

    /**
     applicationWillResignActiveが呼ばれることによって出される信号UIApplicationWillResignActiveNotificationを受信した時に実行する関数
     ロックボタンやタイマーなどでスクリーンが非アクティブになった時に行いたい動作を記述する
     */
    func rcvSignalOfNonActive() {
        print("\(__FUNCTION__) is called!")
    }


}

実行後、ロックボタンなどを押すアクションをするとrcvSignalOfNonActiveメソッドが呼び出されるのが分かります。

自分ポイント3 - その他の通知信号

先にも書いていますが、通知信号が呼ばれるのは今回の非アクティブになるケースだけでなく、UIApplicationDelegateのメッソッドが実行された時にそれぞれ発生します。
そしてそれぞれ出力される通知信号は異なります。
参考URL1 : UIApplicationBackgroundRefreshStatusDidChangeNotification
参考URL2 : UIApplicationのNotificationsの一覧 - 強火で進め

これらの情報を使ってユーザーアクションに対する実行メソッドと信号を実際動かして見てみるの、面白そうです。

3作目のアプリ Advent Canon 2015をリリースしています!

更新の間が開きまくりです。
いろいろ書きたいことが溜まってきています...。

そんな中、前回の記事(アプリ道場 Advent Calendar 2015 9日目を担当して初めてQiitaに投稿した)でお知らせしたアプリが(とっくに)リリースされております。
ご連絡が遅くなって申し訳ありません!!

アプリの説明を...。

音楽と一緒に2015年クリスマスを祝いましょう。

Advent Canon 2015は音楽でクリスマスを祝うアプリ2015年度版です。
クリスマスが近づくにつれて再生できる演奏が増えていきます。
伴奏を含め6つの演奏を組み合わせることで1曲が完成します。
使用した曲はパッヘルベルのカノンです。
以下の日程で再生できる演奏が増えていきます。

・2015年11月29日
・2015年12月06日
・2015年12月13日
・2015年12月20日
・2015年12月24日

クリスマスのアイコンをタップすることで演奏が再生されます。
再度タップすると再生した演奏はミュートされます。
あなたにとって一番心地よい組み合わせを探してみて下さい。

よかったらダウンロードしてみて下さい!楽しいですよ!

それでは皆様良いクリスマスを!

アプリ道場 Advent Calendar 2015 9日目を担当して初めてQiitaに投稿した

お久しぶりです。またまた間が空いてしました。
前回の投稿後、新作アプリを作成していました。
そして昨日無事サブミットが完了しました!
突然新作を作成したきっかけはこれです。

qiita.com

私、これの9日目を担当しました。
記事は以下です。

qiita.com

タイトルや内容を見て頂いたら分かる通り、この為に1本クリスマスアプリを作成しました。 記事中にある通り、Advent Calendarの投稿ネタを考えて街を散策している時にネタが降ってきた感じです。
音声の再生という私にとっては新しいことにも挑戦しました。
「短期間でアプリを作成提出まで持っていきたい」とよく思っていましたので、今回の内容はとても挑戦しがいがありました。
何より憧れのQiitaへの投稿が出来たことへの達成感がすごい!

作成したアプリのGithubは以下にあげておきます。(例によってオープンソースです。)

▶︎GitHub - anthrgrnwrld/adventCanon

f:id:anthrgrnwrld:20151209205533j:plain

またアプリについては詳しく書こうと思っています。
私の投稿とソースが少しでも誰かの役に立ったり、刺激になっていたりすれば嬉しいなー。

UIViewの一部をUIImageとして切り取る方法 [swift2.1] [Context] [CGAffineTransform]

先日はUIImageを切り取る方法について投稿しました。

anthrgrnwrld.hatenablog.com

この記事中の最後でも注意点としても書きましたが、この方法だと 切り取る範囲についてはあくまでUIImageを対象として考えないといけない です。
UIImageはUIImageViewに対し拡大・縮小して表示しているケースが多いので、見たまんまの感覚でrectを指定すると、想定と異なった範囲の切り取り画像になってしまいます。

この問題の解決の為、以下のような方法を考えました。

  1. 対象のViewと切り取り範囲(Rect)を元にコンテキストを作成
  2. 1をUIImageに保存

上記発想の元作成したものについて今回は書きたいと思います。
出来上がったもののgif画像とGithubは以下です。

f:id:anthrgrnwrld:20151120080245g:plain

▶︎GitHub - anthrgrnwrld/clipView

参考リンクは以下です。

▶︎ iPhone アプリ研究会 UIViewの一部をUIImageとして取得する方法

自分ポイント1

切り取り関数のソースを以下に示します。
parameterとしては対象のViewと切り取りRectを指定し、Returnは切り取り後のUIImageとなります。
中の処理のポイントについては 自分ポイント2 にて説明します。

    /**
     対象のViewを指定したrectで切り取りUIImageとして取得する

     - parameter view:切り取り対象のview
     - parameter rect:切り取る座標と大きさ
     - returns: 切り取り結果を返す
    */
    func clipView(view: UIView?, rect: CGRect?) -> UIImage? {
        
        guard let targetView = view else {
            return nil
        }
        
        guard let frameRect = rect else {
            return nil
        }
        
        // ビットマップ画像のcontextを作成.
        UIGraphicsBeginImageContextWithOptions(frameRect.size, false, 0.0)
        let context = UIGraphicsGetCurrentContext()!
        
        //Affine変換
        let affineMoveLeftTop = CGAffineTransformMakeTranslation(-frameRect.origin.x, -frameRect.origin.y)
        CGContextConcatCTM(context, affineMoveLeftTop)
        
        // 対象のview内の描画をcontextに複写する.
        targetView.layer.renderInContext(context)
        
        // 現在のcontextのビットマップをUIImageとして取得.
        let clippedImage = UIGraphicsGetImageFromCurrentImageContext()
        
        // contextを閉じる.
        UIGraphicsEndImageContext()
        
        return clippedImage
    }

自分ポイント2

Parameterの切り取りRectを基にコンテキストを作成します。
コンテキストについてはスクリーンショットを保存する方法の記事で少し出てきました。

anthrgrnwrld.hatenablog.com

スクリーンショットの保存の時にはコンテキストに写す範囲として画面全体としていましたが、今回は必要範囲が決まっています。
そのような場合には以下の様な手順が必要になります。

  1. コンテキスト(一時保存場所?)の作成 → 切り取りサイズの指定
  2. 切り取り座標に従いAffine変換する → 切り取り位置の指定
  3. コンテキストに対象Viewを複写する
// ビットマップ画像のcontextを作成.
UIGraphicsBeginImageContextWithOptions(frameRect.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()!
        
//Affine変換
let affineMoveLeftTop = CGAffineTransformMakeTranslation(-frameRect.origin.x, -frameRect.origin.y)
CGContextConcatCTM(context, affineMoveLeftTop)
        
// 対象のview内の描画をcontextに複写する.
targetView.layer.renderInContext(context)

そしてコンテキストのビットマップをUIImageとして保存します。

let clippedImage = UIGraphicsGetImageFromCurrentImageContext()

ちなみに UIGraphicsBeginImageContextWithOptions を今回使用しましたが、 UIGraphicsBeginImageContext という関数もあります。
しかし今回使用した UIGraphicsBeginImageContextWithOptions の方がRetinaディスプレイを考慮した作りとなっているため、通常はこちらを使用した方が良いと思います。
(参考)
▶︎ 自前で描画した内容がUIImageで ぼやける時の処置 | 秋山ブログ

自分ポイント3

セグコントロールが押下された時の動作のソースを貼っときます。

    /**
     SegControlが押下された時に呼ばれる
    */
    @IBAction func pressClipSegControl(sender: AnyObject) {
        
        var rect: CGRect?       //切り取るrect値格納用
        
        //セグコントロールとclipTypeを紐付け。そしてその値がnilになる場合(= Non Clip)には元のイメージを表示する
        let clipValues : [clipType?] = [nil, .type100x100]
        
        guard let clipValue = clipValues[clipSegControl.selectedSegmentIndex] else {
            imageView.image = UIImage(named: "mountain.jpg")
            return
        }
        
        //セグコントロールが.type100x100(= Clip(100x100))の時、それに従ったrectの値を入れる
        switch clipValue {
        case .type100x100:
            rect = CGRectMake(137, 284, 100, 100)
        }
        
        //imageViewからframeRectで切り取り、結果をUIImageで取得する
        guard let clippedImage = clipView(imageView, rect: rect) else {
            imageView.image = UIImage(named: "mountain.jpg")
            return
        }
        
        //切り取り結果を拡大表示
        imageView.image = clippedImage
        
        
    }

clipTypeはViewController.swiftの頭で以下のように定義しています。

    //clipTypeをenumで定義しておく(一個だけだが練習)
    enum clipType :Int {
        case type100x100
    }

これでUIImageを切り取る方法と比べると直感的に切り取ることが出来るようになりました。