2014年11月12日水曜日

iOS実験専用アプリを活用しよう(1)

皆さんは、初めて作る機能などを試すとき、どのような方法を用いてますか。swiftだから、playgroungですか。でも、UI部品を使う機能などは試せませんよね。私の場合は、実験専用のアプリを用意して、その中で様々な機能を作りながら試しています。このアプリを作ってから、playgroungはほとんど使わなくなりました。そんな実験専用アプリも、少しずつ改良したおかげで、どんどん使いやすくなってきました。アプリの中で使っている工夫も面白いのではないかと思い、皆さんにも紹介します。

 

最初に、専用アプリを用意する理由を取り上げます。まず良い点として、アプリ上で機能を作ると、最終的に使う形と非常に近い構造で作れる点が挙げられます。関数の形で作れば、またはクラスとして作れば、ほとんど直さずに目的のアプリへ持っていけます。

2番目の利点は、テスト環境まで含めて、どんどんと蓄積できることです。試しに作った機能を、後から改良したいと思ったとき、テスト環境まで含めて残っていると、すぐに改良を始められます。前の状態がそのまま残っているので、改良する部分だけを作れば済みます。逆に、新しい機能を試す度に新アプリを作っていたのでは、管理できず、必要なときに見付からないこともあり得るでしょう。でも、いくつもの機能をどんどん追加できる実験専用アプリなら、探す必要はありません。

いくつもの実験ソースコードを作ったとき、管理しやすいのも利点です。新しく作りたい機能が見付かったとき、実験専用アプリに追加していきます。すると、どんどんと機能が蓄積され、1つのアプリに様々な機能を保存できていることになります。探している機能が、どこに付いているのかも明確に示せます。もちろん、様々な機能を一緒に追加しても動くように、実験専用アプリを作る必要があります。まあ、その部分こそ、今回のキモなのですが。

新しい機能を簡単に試せることも、非常に重要です。実験専用アプリでは、追加する機能を試すとき、アプリ起動時に実行する処理や、ボタンをタップしたときに実行する処理など、処理を書く場所などの大枠が最初から用意されています。また、メッセージを表示するUILabelも複数用意してあります。機能を試すために、ボタンやラベルを追加する作業はほとんど不要です。こういった工夫をすることで、新しい機能を素早く作って試せます。

 

様々な利点を挙げたけど、どんな形をしたアプリなの?と疑問を持たれたことと思います。簡単に説明すると、ボタンが40個あり、メッセージ欄も6個あり、自由に使えるView領域を持ったアプリです。何の機能も追加していない、まっさら状態の画面表示は、次のようになっています(シミュレーター上のメニュー機能で画像をコピーしたため、余白が少し多めに追加されたようです。実際の画面表示では、中央の大きな長方形が、画面の左右両端に接しています)。同じ理由で、上下の余白も実際より広くなっています。

画面表示だけで全部を説明することは無理ですが、まずは画面表示を知ってもらわないと、説明が始められません。

 

画面の構成要素を、上から順番に紹介しましょう。一番上の左側にアプリのタイトルがあります。その右にはメッセージ用ラベルがあって、メッセージをクリアーするボタンが並びます。

その下には、合計20個のボタンが並んでいて、それぞれに機能を割り当てて使います。使い方が大事なのですが、それは後で解説します。一番下にもボタンが20個あり、使い方は上のボタンと同じです。上下に半分ずつ分けたのには、ちゃんとした理由があります。追加する機能によっては、テキストフィールドなどのUI部品が必要となります。その場合、ボタンが上下に分かれていると、その近い方に追加でき、追加したUI部品が増えたときに、ボタンとの関係性が理解しやすく配置できます。

上側のボタンの直下には、メッセージ用ラベルが5つあります。長さが違うものを用意して、必要な文字量に応じて使い分けます。5つのうち、一番左側のラベルだけは、決まった機能が割り当てられていて、通常は使えません。

中央にある大きな長方形が、自由に使えるUIViewの領域です。この上に様々なUI部品を配置して、テストすることになります。ただし、操作ボタンとメッセージ欄は既にありますから、それ以外のUI部品が対象です。

 

では、具体的な作り方として、swiftのコードを紹介します。実験をサポートする機能の前に、40個のボタンを生成する方法でも。これだけの数のボタンを作るとき、1つ1つをInterface Builderで描いていたら、大きさを揃えるのも含めて、非常に大変ですよね。私の場合は、Interface Builderを使わずに、UI部品の生成関数を使ってます。

実験専用アプリなので、ソースコードが雑です。その辺を気にせずに見てください。また、これから紹介するソースコードは、すべてViewControllerクラスに入れます。

UIButtonのインスタンスを入れる配列を用意して、20個ずつ2回に分けてボタンを生成しています。配置する座標を計算し、UI部品の生成関数に与えながら生成した後、配列に入れています。2回に分けたのは、配置場所が2つに分かれているからです。

// ボタンを入れる配列
var aryButton : [UIButton] = []
// テスト用のボタン生成
func startTestF() {
    // ボタンのサイズの基本値
    let iW : CGFloat = 95.0
    let iH : CGFloat = 30.0
    // ボタンの位置の基本値
    let iX1 : CGFloat = 15.0
    let addX1 : CGFloat = 100.0
    let iY1 : CGFloat = 60.0
    let addY1 : CGFloat = 40.0
    // 上段のボタン生成
    for i in 1...2 {
        let iY = iY1 + (addY1 * CGFloat(i - 1))
        for j in 1...10 {
            let iX = iX1 + (addX1 * CGFloat(j - 1))
            let iButton = createBtnF("Dummy", 14, iX, iY, iW, iH)
            aryButton.append(iButton)
        }
    }
    // 下段のボタン生成
    let iY2 : CGFloat = 690.0 - 80.0
    for i in 3...4 {
        let iY = iY2 + (addY1 * CGFloat(i - 1))
        for j in 1...10 {
            let iX = iX1 + (addX1 * CGFloat(j - 1))
            let iButton = createBtnF("Dummy", 15, iX, iY, iW, iH)
            aryButton.append(iButton)
        }
    }
    // 全部のボタンに設定(タイトル、アクション、表示基盤)
    for i in 0...39 {
        let iTitle = String(i)
        aryButton[i].setTitle(iTitle, forState: .Normal)
        let iStr = "testBtn" + String(i) + "F:"
        aryButton[i].addTarget(self, action:Selector(iStr), forControlEvents: .TouchUpInside)
        self.view.addSubview(aryButton[i])
    }
}

ボタンを生成して配列に入れた後は、40個のボタンをまとめて処理してます。タイトルをつけ、タップしたときに呼ばれる関数をセットし、ビューに加えてます。間接参照となるビューは使ってません。実験専用アプリだから、手抜きでもOKです。

このような場合こそ、UI部品を生成する関数が大活躍します。生成自体は1行でできますから、for文で回して一括生成しても短いソースコードで済みます。それぞれに配置座標を与え、配列に加えていきます。配列に入れてしまえば、同様にfor文で一括処理できます。

for文で一括生成する方法だと、後からの修正も簡単です。ボタンの大きさを変えたり、全体を上に移動したり、間隔を少し広げたり、文字サイズを変えたりが、1つか2つの数値を変更するだけでできます。何度か実行しながら修正すれば、好みの状態を得られるでしょう。ビジュアル系ツールには難しい芸当です。

 

続きとして、ボタンの使い方を書きたいのですが、それが当テーマのキモのため、長くなりそうなので、その前にメッセージ用ラベルを取り上げます。ボタンと同様に、UI部品の生成関数を使って作ってます。次のようなswiftコードです。行数が増えたのは、ラベルにグレーの枠線を付けたからです。ラベルの区切りを見やすくするために必要だと思いました。

// タイトルと最上部ラベルの生成
var lblMsg : UILabel!
var btnClesr : UIButton!
func startBasic1() {
    let lblTitle = createLblF("iOS各種実験アプリ", 30, ALIGN_LEFT, 20, 10, 300, 40)
    self.view.addSubview(lblTitle)
    lblMsg = createLblF("", 18, ALIGN_LEFT, 290, 15, 655, 30)
    lblMsg.layer.borderWidth = 1
    lblMsg.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg)
    btnClesr = createBtnF("Clear", 16, 950, 15, 60, 30)
    btnClesr.addTarget(self, action:"clearMsgBF:", forControlEvents: .TouchUpInside)
    self.view.addSubview(btnClesr)
}
func clearMsgBF(rSender: UIButton) {
    clearMsgF(0)
}
// ラベルを生成(一番上のlblMsgラベルの生成は含まず)
var lblMsg1 : UILabel!
var lblMsg2 : UILabel!
var lblMsg3 : UILabel!
var lblMsg4 : UILabel!
var lblMsg5 : UILabel!
var viewBase : UIView!
func startBasic2() {
    lblMsg1 = createLblF("", 14, ALIGN_LEFT, 10, 140, 125, 20)
    lblMsg1.layer.borderWidth = 1
    lblMsg1.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg1)
    lblMsg2 = createLblF("", 14, ALIGN_LEFT, 140, 140, 75, 20)
    lblMsg2.layer.borderWidth = 1
    lblMsg2.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg2)
    lblMsg3 = createLblF("", 14, ALIGN_LEFT, 220, 140, 125, 20)
    lblMsg3.layer.borderWidth = 1
    lblMsg3.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg3)
    lblMsg4 = createLblF("", 14, ALIGN_LEFT, 350, 140, 245, 20)
    lblMsg4.layer.borderWidth = 1
    lblMsg4.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg4)
    lblMsg5 = createLblF("", 14, ALIGN_LEFT, 600, 140, 420, 20)
    lblMsg5.layer.borderWidth = 1
    lblMsg5.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(lblMsg5)
    viewBase = createViewF(0, 165, 1024, 515)
    viewBase.layer.borderWidth = 1
    viewBase.layer.borderColor = UIColor.lightGrayColor().CGColor
    self.view.addSubview(viewBase)
}
func setMsgF(rNum:Int, _ rStr:String) {
    switch (rNum) {
    case 0:
        lblMsg.text = rStr
    case 1:
        let strTime = zDate.getStrTimeF()
        lblMsg1.text = strTime + " " + rStr
    case 2:
        lblMsg2.text = rStr
    case 3:
        lblMsg3.text = rStr
    case 4:
        lblMsg4.text = rStr
    case 5:
        lblMsg5.text = rStr
    default:
        lblMsg.text = "ERROR:setMsgF"
    }
}
func addMsgF(rNum:Int, _ rStr:String) {
    switch (rNum) {
    case 0:
        lblMsg.text! += rStr
    case 1:
        lblMsg1.text! += rStr
    case 2:
        lblMsg2.text! += rStr
    case 3:
        lblMsg3.text! += rStr
    case 4:
        lblMsg4.text! += rStr
    case 5:
        lblMsg5.text! += rStr
    default:
        lblMsg.text = "ERROR:addMsgF"
    }
}
func clearMsgF(rNum:Int) {
    switch (rNum) {
    case 0:
        lblMsg.text = " "
    case 1:
        lblMsg1.text = " "
    case 2:
        lblMsg2.text = " "
    case 3:
        lblMsg3.text = " "
    case 4:
        lblMsg4.text = " "
    case 5:
        lblMsg5.text = " "
    default:
        lblMsg.text = "ERROR:clearMsgF"
    }
}

ソースコードの数値を見てわかるように、5つのラベルはどれも長さが違っています。表示する文字列の長さに応じて、適切な長さのラベルを選んで利用します。一応、限られた表示面積をもっとも有効に使えそうな、長さの組み合わせを選びました。なお、テスト途中で手動による内容クリアーが必要な場合は、最上位の消去機能付きラベルを利用します。

ラベル用の関数は共通で3つを付けました。単純に値を入れる、値を追加する、クリアーするの3つです。このうち文字列の最後に文字列を追加する関数は長いラベルで使い、状況の変化を見るのに便利です。

ラベルに値をセットする関数と、クリアーする関数を用意しました。すべてのラベルで共通で使います。ラベルに入れる値ですが、生成時は空の文字列で、クリアーすると空白文字が入ります。クリアーが1度でも実行されていれば、空ではなく空白文字が入るので、空の状態と区別できます。実験アプリなので、空白文字でクリアーする必要もなく、代わりに何かの文字(半角ピリオドとか)を入れる手もありますね。

前述のように、lblMsg1だけが特別で、文字列の前に時刻(時分秒)を追加しています。これが実験のとき、かなり役立つのです。使い方の説明のときに、詳しく解説します。

UIViewのviewBaseを付けた理由も簡単に触れましょう。これがなくてself.viewに付けるとなると、上部のボタンやラベルの分だけ座標位置を計算しなければなりません。縦軸の位置の値がゼロから始められるように、viewBaseを用意しました。また、枠線を表示することで、UI部品を付けられる範囲を示す意味もあります。

 

長くなってきたので、続きは次の投稿にて。

 

(使用開発ツール:Xcode 6.0.1, SDK iOS 8.0)

0 件のコメント:

コメントを投稿