開発メモ

開発関係のメモをいろいろと。たぶん。

iOS向けアプリで複数の画像を切り替え

はじめに

単純に用意した画像を表示するだけで無く、表示する画像をボタンで切り替えられるようにしてみます。

作業の手順としては、こんな感じになります。

  1. 画像を表示するところまで作成
  2. ストーリーボードで切り替えに使うボタンを設置
  3. ストーリーボードでボタンとソースコードを接続
  4. ボタンが押されたとき、画像を切り替える
  5. 表示できる画像の範囲をチェック

ソースコードはこちら。

See_Ku / SwitchingImage — Bitbucket
https://bitbucket.org/See_Ku/switchingimage

動作環境

環境 情報
Xcode 7.3.1 (7D1014)
iOS 9.0
Swift 2.2
Date 2016/7/2

1. 画像を表示するところまで作成

以前、『iOS向けアプリで画像を画面に表示』というネタを書いたので、こちらを参考にしてImage Viewに画像を表示するところまで作業しておきます。

iOS向けアプリで画像を画面に表示 - 開発メモ
http://seeku.hateblo.jp/entry/2016/07/02/160916

今回は複数の画像を切り替えられるようにするので、プロジェクトに複数の画像を追加しておきます。ここでは3枚の画像を追加してみました。

f:id:see_ku:20160702204606p:plain

2. ストーリーボードで切り替えに使うボタンを設置

画像の切り替えに使用するボタンを設置します。今回は前の画像に戻るボタンと、次の画像に進むボタンを使用します。

f:id:see_ku:20160702204620p:plain

ボタンのタイトルはわかりやすい内容に変更しておきましょう。

f:id:see_ku:20160702204632p:plain

ボタンを置く位置やオートレイアウトの設定はお好みでどうぞ。ここでは、こんな感じにしてみました。

f:id:see_ku:20160702204644p:plain

3. ストーリーボードでボタンとソースコードを接続

ボタンが押されたタイミングで、何らかの処理が行えるように接続しておきます。

f:id:see_ku:20160702204657p:plain

ダイアログが表示されるので、接続の詳細を設定します。ConnectionをActionに変えるのをお忘れ無く。

f:id:see_ku:20160702204707p:plain

ボタンが2個あるので、両方とも同じようにして接続しておきます。

この時点で、ViewController.swiftはこんな感じになってます。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!

    @IBAction func onPrev(sender: AnyObject) {
    }

    @IBAction func onNext(sender: AnyObject) {
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let image = UIImage(named: "fruit_melon_cut_orange")
        imageView.image = image
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

4. ボタンが押されたとき、画像を切り替える

複数の画像を切り替えるような処理を行う場合、まず、『表示している画像の番号』を示す変数を作っておいて、それを元に画像を表示させるのが定番の処理です。

こうしておくと、次の画像を表示する処理はこんな感じになります。

  1. 表示している画像の番号を1増やす
  2. 表示している画像の番号を元に画像を表示する

前の画像に戻す処理はこんな感じになります。

  1. 表示している画像の番号を1減らす
  2. 表示している画像の番号を元に画像を表示する

『表示している画像の番号を元に画像を表示する』の部分は、どちらでも同じ処理が使えそうですよね? これをそのままプログラムに落とし込むと、こんな感じになります。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!

    @IBAction func onPrev(sender: AnyObject) {

        // 表示している画像の番号を1減らす
        dispImageNo -= 1

        // 表示している画像の番号を元に画像を表示する
        displayImage()
    }

    @IBAction func onNext(sender: AnyObject) {

        // 表示している画像の番号を1増やす
        dispImageNo += 1

        // 表示している画像の番号を元に画像を表示する
        displayImage()
    }

    /// 表示している画像の番号
    var dispImageNo = 0

    /// 表示している画像の番号を元に画像を表示する
    func displayImage() {

        // 画像の名前の配列
        let imageNameArray = [
            "fruit_melon_cut_orange",
            "fruit_slice10_orange",
            "fruit_slice11_kiwi",
        ]

        // 表示している画像の番号から名前を取り出し
        let name = imageNameArray[dispImageNo]

        // 画像を読み込み
        let image = UIImage(named: name)

        // Image Viewに読み込んだ画像をセット
        imageView.image = image
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        let image = UIImage(named: "fruit_melon_cut_orange")
        imageView.image = image
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

これで一応、画像の切り替えは出来るようになりましたが・・・ 起動直後のPrevボタンを押すと、いきなり落ちます。Nextボタンを何回も押してても落ちます。これではまずいのでどうにかしましょう。

5. 表示できる画像の範囲をチェック

PrevやNextでアプリが落ちるのは、この行が原因です。

        // 表示している画像の番号から名前を取り出し
        let name = imageNameArray[dispImageNo]

dispImageNoをインデックスに使って配列から名前を取り出してますが、この時、dispImageNoが配列の範囲外を指してるとアプリが落ちてしまいます。落ちないようにするため、この前に範囲のチェックを入れましょう。

具体的には、こんな感じになります。

    /// 表示している画像の番号を元に画像を表示する
    func displayImage() {

        // 画像の名前の配列
        let imageNameArray = [
            "fruit_melon_cut_orange",
            "fruit_slice10_orange",
            "fruit_slice11_kiwi",
        ]

        // 画像の番号が正常な範囲を指しているかチェック

        // 範囲より下を指している場合、最後の画像を表示
        if dispImageNo < 0 {
            dispImageNo = 2
        }

        // 範囲より上を指している場合、最初の画像を表示
        if dispImageNo > 2 {
            dispImageNo = 0
        }

        // 表示している画像の番号から名前を取り出し
        let name = imageNameArray[dispImageNo]

        // 画像を読み込み
        let image = UIImage(named: name)

        // Image Viewに読み込んだ画像をセット
        imageView.image = image
    }

実際に動かすとこんな画面になります。

f:id:see_ku:20160702204728p:plain

これで、特に問題なく画像を切り替えることが出来るはずです。たぶん。もっと画像を増やすとどうなるか・・・ 発展課題としてやってみると良いかも知れません。

謝辞

画像はいらすとやさんの物を使わせていただきました。

無料イラスト かわいいフリー素材集 いらすとや
http://www.irasutoya.com/

iOS向けアプリで画像のタップに対応

はじめに

画像をタップして何らかの処理を行うサンプルを作ります。処理の部分は何でも良いんですけど、今回は、別の画面に遷移する処理を行ってみます。

作業の手順としては、こんな感じになります。

  1. 画像を表示するところまで作成
  2. ストーリーボードでTap Gesture Recognizerを追加
  3. ストーリーボードで別の画面を追加しセグエを接続
  4. ストーリーボードでTap Gesture Recognizerとソースコードを接続
  5. 画像がタップされたときの処理で画面を遷移させる

ソースコードはこちら。

See_Ku / TapImageAction — Bitbucket
https://bitbucket.org/See_Ku/tapimageaction

動作環境

環境 情報
Xcode 7.3.1 (7D1014)
iOS 9.0
Swift 2.2
Date 2016/7/2

1. 画像を表示するところまで作成

以前に『iOS向けアプリで画像を画面に表示』というネタを書いたので、こちらを参考にしてImage Viewに画像を表示するところまで作業しておきます。

iOS向けアプリで画像を画面に表示 - 開発メモ
http://seeku.hateblo.jp/entry/2016/07/02/160916

ついでに(?)、ユーザーがタップできるように、Image Viewの設定を変更しておきます。これを忘れてるとハマります。よくあります。

f:id:see_ku:20160702175039p:plain

2. ストーリーボードでTap Gesture Recognizerを追加

ViewControllerの Image Viewの上に Tap Gesture Recognizerをドラッグ&ドロップして追加します。

f:id:see_ku:20160702175052p:plain

Tap Gesture Recognizerは画面に表示されるコントロールでは無いため、ストーリーボードで表示される位置がちょっと変わってます。

f:id:see_ku:20160702175102p:plain

3. ストーリーボードで別の画面を追加しセグエを接続

まず、ストーリーボードで新しくViewControllerを追加します。

f:id:see_ku:20160702175115p:plain

次に、最初の画面と次の画面を接続します。

f:id:see_ku:20160702175126p:plain

セグエの種類を選択します。

f:id:see_ku:20160702175137p:plain

プログラムから使えるように、セグエにIDを設定しておきます。今回は適当に『result』としておきました。

f:id:see_ku:20160702175147p:plain

4. ストーリーボードでTap Gesture Recognizerとソースコードを接続

画像がタップされたとき処理が行えるように、Tap Gesture Recognizerとソースコードを接続します。

f:id:see_ku:20160702175158p:plain

ドロップするとダイアログが出るので、設定を行います。ConnectionをActionにするのを忘れずに。

f:id:see_ku:20160702175213p:plain

今回はonTapImageと言う名前で接続したので、以下のような関数が自動で作られました。

    @IBAction func onTapImage(sender: AnyObject) {
    }

5. 画像がタップされたときの処理で画面を遷移させる

ようやく、ストーリーボードでの作業が一段落着きました。あとは、ソースコードを編集して、次の画面に遷移する処理を書くだけです。

次の画面に遷移する処理は、performSegueWithIdentifier()にセグエのIDを渡すことで実現できます。こんな感じになります。

    @IBAction func onTapImage(sender: AnyObject) {

        // セグエを使用して画面を遷移
        performSegueWithIdentifier("result", sender: nil)
    }

ここまで出来たところで実行すると、こんな画面になります。

f:id:see_ku:20160702175225p:plain

画像をタップすると次の画面に遷移します・・・ が、そこから戻ってくることが出来ません。次の画面は真っ白なので、そもそも、遷移してるのかどうかもわかりにくいです。この辺りは発展課題と言うことで、いろいろ調べてみて下さい。

謝辞

画像はいらすとやさんの物を使わせていただきました。

無料イラスト かわいいフリー素材集 いらすとや
http://www.irasutoya.com/

iOS向けアプリで画像を画面に表示

はじめに

単純に、用意した画像を画面に表示するだけのアプリを作ります。

作業の手順としては、こんな感じになります。

  1. ストーリーボードでImage Viewを設置
  2. ストーリーボードでImage Viewとソースコードと接続
  3. 画像をプロジェクトに追加
  4. 画像を読み込み
  5. Image Viewに画像を設定

ソースコードはこちら。

See_Ku / DisplayImage — Bitbucket
https://bitbucket.org/See_Ku/displayimage/overview

動作環境

環境 情報
Xcode 7.3.1 (7D1014)
iOS 9.0
Swift 2.2
Date 2016/7/2

1. ストーリーボードでImage Viewを設置

まず、適当に新しいプロジェクトを作成します。テンプレートは何でも良いんですけど、とりあえず今回は Single View Applicationを使ってます。

ストーリーボードを開いて、画像を表示するために使うImage Viewを設置します。

f:id:see_ku:20160702160658p:plain

出来れば、ついでに位置を調整してオートレイアウトも設定しておくのが良いでしょう。

2. ストーリーボードでImage Viewとソースコードと接続

ストーリーボードで設置したImage Viewを、ソースコードと接続します。

f:id:see_ku:20160702160713p:plain

接続するプロパティの名前を入力します。ここでは imageView としておきます。

f:id:see_ku:20160702160725p:plain

3. 画像をプロジェクトに追加

実際に表示する画像ファイルをXcodeにドロップして、プロジェクトに追加します。

f:id:see_ku:20160702160737p:plain

ファイルを追加するときのオプション画面が出るので、 Copy items if needed にチェックが付いてるのを確認して、追加します。

f:id:see_ku:20160702160747p:plain

このように、プロジェクトにファイルを追加してプログラムから使えるようにすることを『バンドルする』と言います。プロジェクトに追加されたファイルをまとめて『バンドルファイル』と言ったりもしますね。

4. 画像を読み込み

ここからはソースコードの編集になります。

iOS向けのアプリで画像を取り扱う際、通常は UIImage というクラスを使います。プロジェクトにバンドルした画像ファイルを読み込む場合、こんな感じになります。

        // バンドルした画像ファイルを読み込み
        let image = UIImage(named: "fruit_melon_cut_orange")

この場合、UIImageの引数は拡張子抜きのファイル名になります。JPEGの場合は拡張子が必要だったり、そもそも、Assets.xcassetsを使う場合はそちらの名前だったりするのですが、細かいところは必要に応じて調べましょう。

5. Image Viewに画像を設定

画像ファイルを読み込んだあとは、Image Viewに設定するだけです。Image Viewで表示する画像は、Image Viewの image プロパティで指定します。

        // Image Viewに画像を設定
        imageView.image = image

まとめ

ViewController全体のソースコードはこんな感じになります。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // バンドルした画像ファイルを読み込み
        let image = UIImage(named: "fruit_melon_cut_orange")

        // Image Viewに画像を設定
        imageView.image = image
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

実際に動かすと、こんな感じになるはずです。

f:id:see_ku:20160702160803p:plain

微妙に画像が縦に潰れてたりしますが、これはImage Viewの設定でどうにか出来たりします。その辺りは発展課題と言うことで、いろいろ調べてみて下さい。

謝辞

画像はいらすとやさんの物を使わせていただきました。

無料イラスト かわいいフリー素材集 いらすとや
http://www.irasutoya.com/

詳解Swift 改訂版

詳解Swift 改訂版

種類 情報
著者 萩原剛志
発行日 2016年3月1日 電子版第1刷発行
発行 SBクリエイティブ株式会社

感想

萩原剛志による詳解Swiftシリーズ第2弾。Swiftについて日本語で書かれた資料としては、一番詳しい本。たぶん。


題名の通り『詳解Swift』の改訂版。具体的に言うと元の本がSwift 1.1 / Xcode 6.1で書かれていたのに対して、Swift 2.1 / Xcode 7.1に準じた内容に修正されている。

Swift 1.1からSwift 2.1で変更になった点は、それぞれ、該当するページで注釈が入れてある形。変更点の一覧があってもよかったが。

プロトコル拡張など、Swift 2.1で追加になった機能についてもしっかり説明してある。

kindle版はフリーレイアウト形式になって、検索が自由に使えるようになったのもうれしいところ。ただ・・・ 付録の構文図は不要なのでは? これを見て喜ぶ人がいるのかは疑問。


これからSwiftを使おうと思ってる日本人プログラマーなら、手もとに置いておく価値のある本。問題が起きたときにいちいちネットで検索するぐらいなら、一度真面目にこの本を読んでおく方が時間の短縮になるのでは? 基本的な知識も身につくし。

あえて欠点を上げるとすれば、この秋にSwift 3.0がでて、この本もさらに改訂版が出ることが予想されることかな。そんなことを気にしてるといつまでたっても買えないので、とっとと買ってしまうのが良いと思うけど。

おすすめ指数:☆☆☆☆☆

(2016/06/16 読了)

詳解Swift

詳解Swift

種類 情報
著者 萩原剛志
発行日 2014年12月20日 初版第1刷発行
発行 SBクリエイティブ株式会社

感想

1年以上前に買ってたんだけど、感想を書き忘れてたみたいなので今さらながら書くorz


『詳解 Objective-C』シリーズの著者らしく、Swiftという言語に絞って詳しく説明した本。Swiftについて日本語で書かれた資料としては、一番詳しい内容になってる。たぶん。

じゃあ、どれぐらい詳しいのかと言われると・・・ 元になってるのはAppleの出しているSwift本だったりするから、そっちを直接読める人ならそこまでの価値を感じないかもしれない。


これからSwiftを扱おうと思ってる日本人プログラマーなら手もとに置いておく価値はあるかと。

もちろん、今から買うなら『詳解Swift 改訂版』の方がおすすめ。

『詳解Swift』のKindle版はページ固定レイアウトなのが残念。つまり、単純に紙の本をスキャンしただけの内容になっている。あとから出た『詳解Swift 改訂版』の方は、フリーレイアウト(?)になってるので、そういう意味でも『詳解Swift 改訂版』の方がおすすめ。

おすすめ指数:☆☆☆☆

(2015/06/30ごろ 読了)

次の次の画面に一気に遷移する方法のメモ

UINavigationControllerを使って、次の次の画面に一気に遷移する方法のメモです。

ソースコードはこちら。

See_Ku / SkipViewController — Bitbucket
https://bitbucket.org/See_Ku/skipviewcontroller

動作環境

環境 情報
Xcode 7.3.1 (7D1014)
iOS 9.0
Swift 2.2
Date 2016/6/1

詳細

  1. Storyboardで各ViewControllerにStoryboard IDを設定して、ViewControllerを生成できるようにしておきます
  2. 途中の画面・遷移後の画面のインスタンスを生成します
  3. 遷移元の画面を含める形で、遷移後のViewControllerの配列を生成します
  4. setViewControllers()で遷移します

ソースコードはこんな感じになります。

    /// 次の次の画面に一気に遷移
    @IBAction func onSkipViewController(sender: AnyObject) {

        // 次の画面を作成
        let vc2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2")

        // 次の次の画面を作成
        let vc3 = storyboard?.instantiateViewControllerWithIdentifier("ViewController3")

        // 遷移後のViewControllerの配列を作成
        let ar = [self, vc2!, vc3!]

        // まとめて設定
        navigationController?.setViewControllers(ar, animated: true)
    }

簡単ですね。

Swiftにおけるselfとクロージャーと@noescape

Swiftにおけるselfとクロージャーと@noescapeと簡単にまとめてみました。

※Swift 2.1時点での情報です

動作を確認した環境

環境 情報
Xcode 7.2.1 (7C1002)
iOS 9.2
Swift 2.1.1
Date 2016/3/10

用語説明

selfとは?

公式の情報は The Swift Programming Language (Swift 2.1): Methods の『The self Property』にあります。

Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.

簡単に訳すと、

selfと呼ばれるプロパティを全てのインスタンスが持ってます。それは、自分自身を現します。インスタンスメソッドの中で、現在のインスタンスを参照するためにselfプロパティを使う事が出来ます。

といった感じでしょうか。つまり、自分自身ですね。

クロージャーとは?

公式の情報は The Swift Programming Language (Swift 2.1): Closures にあります。

Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.

これは

クロージャーは自分自身を含んだ関数のようなブロックで、他に渡してコードから使う事が出来ます。Swiftのクロージャーは、CやObjective-Cにおけるのブロックや他の言語で言うところのラムダ式に似てます。

といった感じでしょうか? 関数のようなブロックなんですね。それだけで無くクロージャーには、作られたときの周囲の定数や変数をキャプチャーする働きもあったりします。

この辺りの話は The Swift Programming Language (Swift 2.1): Closures を参照して下さい。

@noescapeとは?

公式の情報は The Swift Programming Language (Swift 2.1): Attributes の『Declaration Attributes』にあります。

Apply this attribute to a function or method declaration to indicate that a parameter will not be stored for later execution, such that it is guaranteed not to outlive the lifetime of the call. Function type parameters with the noescape declaration attribute do not require explicit use of self. for properties or methods. For an example of how to use the noescape attribute, see Nonescaping Closures.

面倒なのでいちいち訳しませんが、簡単に言うと、noescape属性をつけたら渡された引数(クロージャー)を保存したり後から呼び出したりしないよ、と。そういう事ですね。そして、具体的な使い方は The Swift Programming Language (Swift 2.1): Closures にありますよ、と。

selfの具体的な使用例

同じ名前のローカル変数でプロパティが隠されてるときに使用する

明示的にプロパティを使うためにselfをつけるケースですね。init()でよく使われる形です。

class JunkClass1
    var name = ""
    init(name: String) {

        // 引数のnameで隠蔽されているため、プロパティを参照する方にselfを使用
        self.name = name
    }
}

自分自身を他の変数に渡すときに使う

自分自身を変数のように扱うケースですね。例はUITableViewを使うときのお約束の形です。

class JunkClass2: NSObject, UITableViewDataSource, UITableViewDelegate {
    func setupTableView(tableView: UITableView) {

        // 自分自身をdelegateやdataSourceに設定するため、selfを使用
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    以下略

クロージャーの中でプロパティを参照するために使う

クロージャーの中で自分自身のプロパティやメソッドを呼び出すために、明示的にselfをつけるケースですね。

用語説明ではさらっと流しましたが、クロージャーは周囲の環境をキャプチャーしてくれます。では、具体的に何をキャプチャーしてくれるのかというと、そのクロージャーの中で使用している定数や変数です。

ただ、暗黙のうちに使用しているselfまではキャプチャーしてくれないので、明示的に使用してキャプチャーしてもらうわけですね。たぶん。

class JunkClass3
    var name = ""

    /// クロージャーを使用する例
    func useClosure() {

        let no = 20

        // 接尾形式でクロージャーを渡す
        execClosure() {

            // 自動でキャプチャーされるのでそのまま使用可能
            print("no: \(no)")

            // この場合、この書き方ではエラーになる
            // → 周囲の環境にnameが見つからないため。かな?
//         print("name: \(name)")

            // クロージャーの中でプロパティを参照するためにselfを使用
            // → selfがキャプチャーされ、self.nameが使用可能になる
            print("name: \(self.name)")
        }
    }

    /// 渡されたクロージャーを呼び出すだけのメソッド
    func execClosure(closure: (()->Void)) {
        closure()
    }
}

selfをつける事でプロパティが使えるようになってめでたしめでたし・・・ と終わればよかったんですが、これはこれで、循環参照といった問題を生み出す事になってしまいます。

循環参照を簡単に説明すると、蛇が自分のしっぽを食べようとするようなお話です。

『selfが何かプロパティを持ってて、そのプロパティがクロージャーを持ってて、そのクロージャーがselfをキャプチャーしてる』

といった感じですね。循環参照の何が悪いかというとメモリリークが起きる事で、これを回避しようと思ったら・・・ この話は果てしなく長くなるので、ここでは省略します。

@noescapeの使用例

クロージャーの中でいちいちselfを付けるのが面倒』とか『循環参照対策が面倒』と言った意見に応えて作られたのが@noescape属性です。たぶん。・・・あれ? 循環参照対策にはなってない?

@noescape属性が付けられたクロージャーはそのメソッドの中でしか使われません。 これが何を意味してるかというと、メソッドを抜けた時点で、キャプチャーしていた定数や変数を解放する事が出来るようになるという事です。これによって循環参照は発生しなくなりました。

また、キャプチャーしても循環参照が発生しない事がはっきりしてるので、サービスとして(?)自動的にselfをキャプチャーしてくれるようになります。つまり、クロージャーの中でいちいちselfを付けなくてもよくなります。

class JunkClass4 {
    var name = ""

    /// @noescapeのクロージャーを使用する例
    func useClosureNoescape() {

        let no = 20

        // 接尾形式でクロージャーを渡す
        execClosureNoescape() {

            // ローカル変数の動作は同じ
            print("no: \(no)")

            // 自動でselfをキャプチャーしてくれるので、そのまま使用可能
            print("name: \(name)")

            // 明示的にselfを付けても動作は同じ
            print("name: \(self.name)")
        }
    }

    /// 渡されたクロージャーを呼び出すだけのメソッド
    /// → 引数に@noescape属性を付けただけ
    func execClosureNoescape(@noescape closure: (()->Void)) {

        // @noescapeが付いてるので、ここでしか使えない
        closure()
    }
}

@noescapeの使い方は、クロージャーを受け取る側で引数に@noescape属性を付けるだけです。簡単ですね。

出来れば積極的に使っていきたい@noescapeですが、残念ながら、使用できる場所は限られています。何かイベントが発生したときに呼び出されるようなクロージャーを、@noescapeにすることは出来ません。

そういうときは・・・ がんばって、普通のクロージャーを使いこなしましょう。