シンプル電卓アプリ

講義の目標

  • プログラムコードのみでアプリの画面レイアウトが作成できるようになること

概要

手軽に作成できる実用的なアプリとしておすすめなのが電卓アプリです。この電卓アプリではプログラムコードのみでアプリの画面レイアウトをしていきます。

アプリの完成イメージ

完成品のサンプルは以下よりダウンロードできます。イメージが湧かない方は是非ご確認ください。

iOS完成品サンプル(シンプル電卓アプリ)

新規プロジェクト作成

まずは、電卓アプリ用プロジェクトを作成します。Xcodeを起動して「File」-「New」-「Project…」を選択して、新規プロジェクト選択画面を表示させます。

Xcodeを起動させて「Command + Shift + n」でもプロジェクト作成画面が表示されます。

使用するテンプレートは「Single View Application」となります。

プロジェクト設定画面では次のように設定し、「Next」ボタンを押してください。

入力項目
Product Name Calclator
Team None
Organization Name 任意(”ALJ”など自分が所属している組織の名前を入れる)
Organization Identifier jp.co.al-j
Language Swift
Devices iPhone
Use Core Data チェックを外す
include Unit Tests チェックを外す
include UI Tests チェックを外す

次にプロジェクト保存場所を指定する画面になりますので任意の場所を指定して「Create」を押してプロジェクトを作成します。ソースコードをGitリポジトリで管理しない場合は、「Create Git repository no…」チェックは外しておきましょう。

「Calclator」プロジェクトが作成されるとワークスペースウィンドウが表示されます。電卓アプリを作っていきましょう。

アプリ設定

今回使用する外部素材ファイルを一気にインポートしてしまいましょう。

素材ダウンロード(Calclator)

今回使用する素材は以下の通りです。

ファイル名 説明
iPhone-Notification-20@2x.png Notification用x2画像
iPhone-Notification-20@3x.png Notification用x3画像
iphone-icon-60@2x.png アイコンx2画像
iphone-icon-60@3x.png アイコンx3画像
iphone-setting-29@2x.png 設定x2画像
iphone-setting-29@3x.png 設定x3画像
iphone-spotlight-40@2x.png Spotlight用x2画像
iphone-spotlight-40@3x.png Spotlight用x3画像
splash-iphone-2x.png スプラッシュ画像x2画像
splash-iphone-ios7-retina4.png スプラッシュiOS7画像
splash-iphone-ios8-47.png スプラッシュiOS8画像 4.7インチ画像
splash-iphone-ios8-55-portrait.png スプラッシュiOS8 5.5インチ画像

アイコンの設定

Project Editorを開き「General」を選択し「App Icon」の矢印マークをクリックしてアイコンをセットしてください。

スプラッシュ画像設定

画像素材のsplashフォルダの中のスプラッシュ画像をプロジェクトに取り込んでください。

オリエンテーションの設定

今回は縦向き固定にします。

ホーム画面のアプリタイトル

Project Editorの「info」タブをクリックして「Bundle Name」の項目を次のように変更していきます。

設定項目 設定値
Bundle Name シンプル電卓

StoryBoardを使わないアプリ開発手法

今まで画面レイアウトを構築する場合にはStoryBoardファイルをIB(Interface Builder)で開いてレイアウトしてきました。今回のアプリはStoryBoardを使わずにプログラムコードで画面のレイアウトを構築していきます。

StoryBoardを使わないアプリ開発について

なぜ今回StoryBoardを使わないのかというと、StoryBoardを使わなくてもアプリは作れるという事を是非知ってもらいたいからです。もちろんStoryBoardはとてもよく出来ていて、Storyboardを使ったほうが効率的いい場面はたくさんあります。しかしプログラムコードのみでもStoryBoardを使った場合と同じようにアプリを作れますし、たくさんの要素を配置しなければいけない場合や、色々なイベントで様々な要素を表示させたいという場合には、StoryBoardで表現するよりもプログラムで要素を作ったほうが効率がいい場合もあります。

また、チーム開発など、複数のメンバーで同じアプリを作る場合にStoryBoardを使ってしまうと、複数の開発者が同じタイミングでStoryBoardを修正してしまう事になると、色々と不都合だったりもします。

ですので今回はStoryBoardを使わないで画面の要素を作り出し表示させながら電卓アプリを作っていきたいと思います。

計算結果表示ラベルの配置

まずは計算結果を表示させるラベルをプログラムコードで配置していきます。ViewController.swiftファイルを開いて下記のとおりに記述してください。

ViewController.swift

import UIKit

class ViewController: UIViewController {

// ▼▼ 追加 ▼▼
// 計算結果ラベル
let resultLabel :UILabel = UILabel();
// 画面全体の横幅
let screenWidth :Double = Double(UIScreen.main.bounds.size.width)
// 画面全体の縦幅
let screenHeight :Double = Double(UIScreen.main.bounds.size.height)

// ▲▲ 追加 ▲▲

override func viewDidLoad() {
// ▼▼ 追加 ▼▼
super.viewDidLoad()
// 計算結果ラベルを配置
resultLabel.frame = CGRect(
x: 10,
y: 30,
width: screenWidth - ( 10 * 2 ),
height: 170
)
// 計算結果ラベルの背景色を灰色にする
resultLabel.backgroundColor = UIColor.gray
// フォントサイズ指定
let font:UIFont = UIFont(name:"Arial",size:50)!
resultLabel.font = font
// テキストを右揃え
resultLabel.textAlignment = .right
// 最大行数を4行に変更
resultLabel.numberOfLines = 4
// 初期値を"0"
resultLabel.text = "0"
// 画面に配置
self.view.addSubview(resultLabel)
// ▲▲ 追加 ▲▲
}
}

コードの解説

プログラムでラベルを配置する

プログラムでラベルのようなView要素を配置するには下記のように記述します。

ViewController.swift

Viewインスタンス.frame = CGRect(
x: X座標の値,
y: X座標の値,
width: 横幅の値,
height: 縦幅の値,
)
配置する先のViewインスタンス.addSubview(Viewインスタンス)

このようにViewインスタンスのframeプロパティにX座標、Y座標、横幅、縦幅を指定する事により、配置したいView要素の配置場所とサイズを設定する事ができ、View要素のaddSubviewメソッドを使って実際にView要素を配置する事ができます。

iOSにおける画面の座標について

今回はこのようにself.viewという画面全体を覆っているViewインスタンスにラベルを「X座標:10」、「Y座標:30」、「横幅:(画面全体の横幅 - ( 余白 x 2 ))」、「縦幅:170」で配置しています。

// 計算結果ラベルを配置
resultLabel.frame = CGRect(
x: 10,
y: 30,
width: screenWidth - ( 10 * 2 ),
height: 170
)
:
:
// 画面に配置
self.view.addSubview(resultLabel)

iOSの画面のサイズの単位について

frameプロパティで設定した数値の単位はpt(ポイント)です。
ピクセルとポイントの違いについては下記のとおりです。

*ピクセルとポイントについて
(Apple公式ドキュメント「iOSヒューマンインタフェイスガイドライン」より)
ピクセルとは、画像編集アプリケーションにおいて、デバイス画面のサイズや作成するアイコンのサイズを検討する場合に使用する測定単位です。ポイントとは、画面上の描画領域のサイズを検討する際に使用する測定単位です。
標準解像度のデバイスの画面では、1ポイントが1ピクセルに対応しますが、それ以外の解像度では対応の割合が異なる場合があります。たとえば、Retinaディスプレイでは1ポイントは2ピクセルに相当します。

ラベルの背景色・行揃えの指定

ラベルが配置された事を確認するため、とりあえず背景色を灰色、初期値は「0」にしてみます。
(見た目は後で調整します。)

ViewController.swift

// 計算結果ラベルの背景色を灰色にする
resultLabel.backgroundColor = UIColor.gray
resultLabel.textAlignment = .right
resultLabel.numberOfLines = 4
resultLabel.text = "0"

下記のように表示されればOKです。

ボタンの配置

次に電卓のボタン部分をプログラムコードで作っていきます。

電卓アプリのようにたくさんのボタンを規則正しく配置する場合にはStoryBoardを使って配置するより効率的かつ正確に配置できます。

プログラムコードでボタン部分の配置するための考え方

ボタンを配置するためのプログラムコードを書く前にどのように、どのようなプログラムコードでボタンを配置するかを整理しましょう。今回の電卓アプリの場合は画面レイアウトを紙やノートなどでまず描いてみる事をおすすめします。

今回の電卓アプリは次のような画面レイアウトとして構築していきます。

一つのボタンの大きさの計算式を作る

それではこの画面レイアウトに従って1つのボタンの大きさを求めてみましょう。

ボタンのサイズを求める

iPhoneの画面サイズに応じてボタンのサイズを変更できるようにしてみたいと思います。
一つのボタンの縦幅(Height)、横幅(Width)は次の計算式で算出できます。

ボタンの横幅 = (画面全体の横幅)- (ボタン間の余白 x ( 1行に配置するボタン数 + 1 ) ) ÷ 1行に配置するボタン数
ボタンの縦幅 = (画面全体の縦幅)- (計算結果表示エリアの縦幅) - (ボタン間の余白 x ( 1列に配置するボタン数 + 1 )) ÷ 1列に配置するボタン数

<レイアウト図と計算式を表示させた図>

このように画面レイアウトをじっくりみて、配置する要素がどのような計算式で算出できるかを考えるのがポイントです。

この計算式のとおり、ボタンのサイズを画面サイズに応じて変更するためには下記のデータを決めればいいという事になります。

  • 1行に配置するボタンの数
  • 1列に配置するボタンの数
  • 画面全体の横幅
  • 画面全体の縦幅
  • ボタン間の余白(縦)
  • ボタン間の余白(横)
  • 計算結果表示エリアの縦幅

それではこのデータが必要という事がわかったら実際にコーディングする前にプログラムファイルにコメントを入れておきましょう。

ViewController.swift

class ViewController: UIViewController {
// 計算結果ラベル
let resultLabel :UILabel = UILabel();
// 画面全体の横幅
let screenWidth :Double = Double(UIScreen.main.bounds.size.width)
// 画面全体の縦幅
let screenHeight :Double = Double(UIScreen.main.bounds.size.height)


▼▼ 追加 ▼▼
// 1行に配置するボタンの数
// 1列に配置するボタンの数
// ボタン間の余白(縦)
// ボタン間の余白(横)
// 計算結果表示エリアの縦幅
// ▲▲ 追加 ▲▲
ワンポイントアドバイス

オリジナルアプリを作る場合にはこのように自分が考えやコンセプトをプログラムとして表現する必要があります。しかし、いくらプログラミングの経験をつんだとしても最初から完璧なプログラムコードを頭に描いて書く事はなかなかできません。そこで私がおすすめするのがまず「考えている事をプログラムファイルにコメントとして書く」という事です。まず最初に自分が開発したい事を文章化しプログラムファイルにコメントとして記述しておけば、どんどん自分の頭の中が整理され、どこにどのようなプログラムを記述すればいいかが明確になります。

ボタンの数について

今回は横に4つ、縦に4つの計16個のボタンを下記のように配置します。

7 8 9 x
4 5 6 -
1 2 3 +
0 C ÷ =

ですので下記のような変数を定義する事にします。

// 1行に配置するボタンの数
let yButtonCount = 4
// 1列に配置するボタンの数
let xButtonCount = 4

ボタン間の余白について

今回はボタン間の余白は任意に決めたいと思います。ボタン間の余白は今回は10ポイントにします。

let buttonMargin :Double = 10.0 

計算結果表示エリアの縦幅

計算結果表示エリアの縦幅は端末の画面サイズに合わせて下記のように変更できるようにしてみたいと思います。

端末の大きさ 計算結果表示エリアの縦幅
3.5インチ 200pt
4インチ 250pt
4.7インチ 300pt
5.5インチ 350pt
// 計算結果表示エリアの縦幅
var resultArea :Double = 0
:
:
// 画面全体の縦幅に応じて計算結果表示エリアの縦幅を決定
switch screenHeight {
case 480:
    resultArea = 200.0
case 568:
    resultArea = 250.0
case 667:
    resultArea = 300.0
case 736:
    resultArea = 350.0
default:
    resultArea = 400.0
}

コードの記述

それでは今までの方針に合わせてプログラミングしてみます。

ViewController.swift

class ViewController: UIViewController {
// 計算結果ラベル
let resultLabel :UILabel = UILabel();
// 画面全体の横幅
let screenWidth :Double = Double(UIScreen.main.bounds.size.width)
// 画面全体の縦幅
let screenHeight :Double = Double(UIScreen.main.bounds.size.height)

// ▼▼ 追加 ▼▼
// 1行に配置するボタンの数
let yButtonCount = 4
// 1列に配置するボタンの数
let xButtonCount = 4
// ボタン間の余白(縦と横を兼用)
let buttonMargin :Double = 10.0
// 計算結果表示エリアの縦幅
var resultArea :Double = 0
// ▲▲ 追加 ▲▲

override func viewDidLoad() {
super.viewDidLoad()

// ▼▼ 追加 ▼▼
// 画面全体の縦幅に応じて計算結果表示エリアの縦幅を決定
switch screenHeight {
case 480:
resultArea = 200.0
case 568:
resultArea = 250.0
case 667:
resultArea = 300.0
case 736:
resultArea = 350.0
default:
resultArea = 400.0
}
// ▲▲ 追加 ▲▲

// 計算結果ラベルを配置
resultLabel.frame = CGRect(
x: 10,
y: 30,
width: screenWidth - ( buttonMargin * 2 ),
height: resultArea - 30
)
:
(省略)
:
self.view.addSubview(resultLabel)

// ▼▼ 追加 ▼▼
// ボタンを配置
// 繰り返し処理でボタンを配置
for y in 0 ..< yButtonCount {
for x in 0 ..< xButtonCount {
// ボタンインスタンス生成(この時点ではサイズ、配置場所はきめていない)
let button :UIButton = UIButton()
// ボタンのx軸の配置
let buttonPositionX =
( screenWidth - buttonMargin ) / Double(xButtonCount) * Double(x) + buttonMargin
let buttonPositionY =
( screenHeight - resultArea - buttonMargin ) / Double(yButtonCount) * Double(y) + buttonMargin + resultArea
// ボタンサイズ
let buttonWidth =
( screenWidth - ( buttonMargin * ( Double(xButtonCount) + 1 ) ) ) / Double(xButtonCount)
let buttonHeight =
( screenHeight - resultArea - ( ( buttonMargin * Double(yButtonCount) + 1 ) ) ) / Double(yButtonCount)
// ボタンの配置場所、サイズを設定
button.frame = CGRect(x:buttonPositionX,y:buttonPositionY,width:buttonWidth,height:buttonHeight)
// ボタン背景色設定
button.backgroundColor = UIColor.green
// ボタン配置
self.view.addSubview(button)
}
}
// ▲▲ 追加 ▲▲

コードの解説

ボタンのサイズを求めるために必要なデータを下記のように定義しています。

// 画面全体の横幅
let screenWidth :Double = Double(UIScreen.main.bounds.size.width)
// 画面全体の縦幅
let screenHeight :Double = Double(UIScreen.main.bounds.size.height)
// 1行に配置するボタンの数
let yButtonCount = 4
// 1列に配置するボタンの数
let xButtonCount = 4
// ボタン間の余白(縦と横を兼用)
let buttonMargin :Double = 10.0
// 計算結果表示エリアの縦幅
var resultArea :Double = 0
定数名 データ型 説明
screenWidth 少数 画面全体の横幅
screenHeight 少数 画面全体の縦幅
yButtonCount 整数 1行に配置するボタンの数
xButtonCount 整数 1列に配置するボタンの数
buttonMargin 少数 ボタン間の余白(縦と横を兼用)
resultArea 少数 計算結果表示エリアの縦幅

画面サイズによっては一つのボタンサイズの値が小数点が含まれる場合がありますので、計算は少数(Double型)で行うため、ほとんどのデータを少数にしています。1行及び1列に配置するボタン数(xButtonCount、yButtonCount)については繰り返し構文のfor文の条件として利用するため、整数(Int型)にしています。

繰り返し構文(for文)を使ってボタンを配置

今回の電卓アプリのボタンは合計で16個あります。これを先ほどの計算結果表示用のラベルのように一つずつaddSubviewメソッドを記述してしまうと、プログラムコードが複雑になり、後で修正する時にも大変です。さらにはバグがでやすい状態にもなります。
今回のようにある一定の法則でViewを連続して配置する場合にはfor文などで繰り返し処理を使い配置するとプログラムコードがすっきりします。

// ボタンを配置(繰り返し処理)
for y in 0 ..< yButtonCount {
for x in 0 ..< xButtonCount {
(ボタンを配置する処理)
}
}

今回はY軸に配置するボタンの数(今回は4つ)と、X軸に配置するボタンの数(今回は4つ)をfor文を使って繰り返し処理をし、合計16個のボタンを生成して配置するプログラムを記述しています。

ボタンを所定の場所に配置する処理は下記のプログラムで配置しています。
ここのプログラムでループする毎に配置する座標を変更しています。

// ボタン配置のX座標
let buttonPositionX = ( screenWidth - buttonMargin ) / Double(xButtonCount) * Double(x) + buttonMargin
// ボタン配置のY座標
let buttonPositionY = ( screenHeight - resultArea - buttonMargin ) / Double(yButtonCount) * Double(y) + buttonMargin + resultArea

変更(座標を移動する)計算は下記の計算式です。

ボタンのX座標 = ( 画面全体の横幅 - ボタン間の余白 ) ÷ 1行のボタン数 × 現在のボタンの列数 + ボタン間の余白
ボタンのY座標 = ( 画面全体の横幅 - ボタン間の余白 ) ÷ 1列のボタン数 × 現在のボタンの行数 + ボタン間の余白

もう少し詳しく解説すると、ボタンのX座標に配置する処理を例にした場合、下図のとおり画面全体の横幅からボタン間の余白1つ分を差し引いた状態で1行のボタン数で割り算をすると「一つのボタンと一つの余白をセットにしたものの横幅」が決まり、ループ処理するごとに移動する距離が算出できます。

そこから実際に配置する場所を余白分右にづらす事により、余白を考慮して4等分のX座標が繰り返し処理が実行する毎に移動して配置される事になります。同じ考えでY座標の配置も同じ計算式で配置しています。

ボタンサイズの決定

ボタンサイズは下記の計算式で算出しています。

ボタン横幅 = 画面全体の横幅 - ( ボタン間の余白 × ( 1行のボタン数 + 1 ) ) ÷ 1行のボタン数
ボタン縦幅 = 画面全体の縦幅 - ( ボタン間の余白 × ( 1列のボタン数 + 1 ) ) ÷ 1列のボタン数

この計算式をプログラムにしたのが下記になります。
xButtonCountとyButtonCountは整数(Int)のデータの型ですので、他のデータと型を合わせるためにDouble型に変換しています。

// ボタンサイズ
let buttonWidth = ( screenWidth - ( buttonMargin * ( Double(xButtonCount) + 1 ) ) ) / Double(xButtonCount)
let buttonHeight = ( screenHeight - resultArea - ( ( buttonMargin * Double(yButtonCount) + 1 ) ) ) / Double(yButtonCount)

Int型のデータをDouble型に変換

Double(xButtonCount)
Double(yButtonCount)

ボタンの配置

繰り返し処理の最後にはラベルと同様に、UIButtonインスタンスのframeプロパティにX座標、Y座標、横幅、縦幅をセットして生成し、addSubviewメソッドで親Viewに配置しています。

// ボタン配置
button.frame = CGRect(
x:buttonPositionX,
y:buttonPositionY,
width:buttonWidth,
height:buttonHeight
)
button.backgroundColor = UIColor.green
self.view.addSubview(button)

とりあえずこの状態でビルドして確認してみましょう。このような表示になっていればOKです。

ボタンにラベルを入れよう

さて、電卓アプリのボタンを画面に配置する事ができました。次はそのボタン一つ一つにラベルをつけていきます。下記のとおりプログラムを追加してください。

ViewController.swift

// ボタンを配置
///////////// ▼追加▼ /////////////
// ボタンのラベルを配列で用意
let buttonLabels = [
"7","8","9","×",
"4","5","6","-",
"1","2","3","+",
"0","C","÷","="
]
///////////// ▲追加▲ /////////////
// 繰り返し処理でボタンを配置
for y in 0 ..< yButtonCount {
for x in 0 ..< xButtonCount {
// ボタンインスタンス生成(この時点ではサイズ、配置場所はきめていない)
let button :UIButton = UIButton()
///////////// ▼追加▼ /////////////
// 各ボタンの番号設定
let buttonNumber = y * xButtonCount + x
// 各ボタンのラベル設定
button.setTitle(buttonLabels[buttonNumber], for: .normal)
///////////// ▲追加▲ /////////////
// ボタンのx軸の配置

コードの解説

今回の電卓アプリのボタンは繰り返し処理で配置しましたので、ボタンのラベルも繰り返し処理の中で設定できるようにします。まず繰り返し処理をする前にボタンラベル用の配列を用意します。

// ボタンのラベルを配列で用意
let buttonLabels = [
"7","8","9","x",
"4","5","6","-",
"1","2","3","+",
"0","C","÷","="
]

この配列は文字列型の配列でボタンの数と同じ16個用意しています。
また、配列の順番も繰り返し処理でボタンが作成され、配置される順番でラベルを用意しています。
わかりやすくするために配列の要素4つづつ改行して、ボタンの配置と対応させています。

その配列を繰り返し処理の中で次のように記述し、ボタンが生成される毎にラベルをセットするようにしています。

// 各ボタンの番号設定
let buttonNumber = y * xButtonCount + x
// 各ボタンのラベル設定
button.setTitle(buttonLabels[buttonNumber], for: .normal)

この状態でビルドしてみましょう。下記のように各ボタンにラベルが表示されればOKです。

ボタンをタップしたときの処理

次にボタンをタップしたときの処理を追加します。
繰り返し処理の箇所で次のように記述してください。

// ボタン背景色設定
button.backgroundColor = UIColor.green

///////////// ▼追加▼ /////////////
// ボタンタップ時のアクション設定
button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
///////////// ▲追加▲ /////////////

// ボタン配置
self.view.addSubview(button)

addTargetメソッドを使う事によりボタンに対してイベントが発生した場合のアクション先を指定する事ができます。

View要素.addTarget(ターゲット,action:アクション,forControlEvents:イベント)
引数 説明
ターゲット アクションを実行するオブジェクトを指定します。
アクション アクションはイベント発生時に実行したメソッドを指定します。(※)
イベント イベントは下記の表のとおり様々な種類を指定する事ができます。

イベントの種類

イベントの種類 説明
UIControlEventTouchDown タッチダウン(タッチした時点)
UIControlEventTouchDownRepeat 複数回のタッチダウン(タッチした時点)
UIControlEventTouchUpInside コントロール内でのタッチアップ(タッチして離した時点)
UIControlEventTouchUpOutside コントロール外でのタッチアップ(タッチして離した時点)
UIControlEventTouchDownRepeat 複数回のタッチダウン(タッチした時点)

ボタンアクション設定

addTargetメソッドで”buttonTapped:”のアクションを指定しましたので、buttonTappedメソッドを実装します。

///////////// ▼追加▼ /////////////
// ボタンがタップされた時
func buttonTapped(_ sender: AnyObject){
print("buttonTapped!!")
}
///////////// ▲追加▲ /////////////

この状態でビルドしてみましょう。下記のようにコンソールエリアに「ボタンタップ!!」が表示されれば、ボタンのイベント(TouchUpInside)を検知してメソッドが実行されているという事になります。

各ボタンを押したときの処理

ボタンのイベントによりメソッドを実行できるようになりましたが、「どのボタンを押されたか?」という事を検知しないといけません。どのように検知すればよいでしょうか。

今回はタップされたボタンのラベル(タイトル)の文字列を取得して判別するようにしてみます。

ViewController.swift

// ボタンがタップされた時
func buttonTapped(_ sender: AnyObject){
print("buttonTapped!!")
///////////// ▼追加▼ /////////////
let tappedButton:UIButton = sender as! UIButton
let tappedButtonTitle:NSString = tappedButton.currentTitle! as NSString
print("タップしたボタン:\(tappedButtonTitle)")
///////////// ▲追加▲ /////////////
}

この状態でビルドし、iOSシュミレーターで動作確認してみましょう。
タップしたボタンのラベルがコンソールエリアに表示されていればOKです。

このようにaddTargetメソッドで呼び出されたメソッドは第一引数に、イベントが発生したView要素(今回はUIButton)が入ります。その仕組みを利用してタップされたボタンのラベルを取得しています。

タップされたボタンの種類によって処理を分ける

それでは電卓の機能を実装するための各ボタンがタップされた場合の具体的な処理を記述していきます。

まず必要な要素としては計算する対象を一時的に保存するための変数が必要ですね。
今回は計算対象の数字を管理する変数をnumber、計算結果を格納する変数をresultとします。

電卓計算に必要な変数

変数名 データ型 説明
number Double型 計算する数字を管理する変数
result Double型 計算結果の数字を管理する変数
operator String型 どのような計算(加減乗除)をするかを管理する変数

それでは早速コメントとともにプログラムファイルに記述しておきましょう。

ViewController.swift

class ViewController: UIViewController {
:
:
///////////// ▼追加▼ /////////////
// 入力数字
var number:Double = 0
// 計算結果
var result:Double = 0
// 演算子
var operatorId:String = ""
///////////// ▲追加▲ /////////////

override func viewDidLoad() {

なぜDouble型で計算するの?

整数型はSwiftでは下記の種類の整数型のデータ型が存在します。

符号付き整数型

データ型 説明
Int8 -128〜127の整数
Int16 -32,768〜32,767の整数
Int32 -2,147,483,648〜2,147,483,647の整数
Int64 -9,223,372,036,854,775,808〜9,223,372,036,854,775,807の整数
Int 32ビット環境ではInt32、64ビット環境ではInt64と同じ範囲の整数値をとる

符号無し整数型

データ型 説明
UInt8 0〜255 の整数
UInt16 0〜65,535 の整数
UInt32 0〜4,294,967,295の整数
UInt64 0〜18,446,744,073,709,551,615の整数
UInt 32ビット環境ではInt32、64ビット環境ではInt64と同じ範囲の整数値をとる

浮動小数点

データ型 説明
Float 32ビット浮動小数点。Doubleほどの精度を必要としない場合に使用する
Double 64ビット浮動小数点。小数点を扱う場合は主にこちらを使用する

整数型で計算すると大きな桁数の計算をしてしまうと、すぐ許容量以上になってしまい、アプリが落ちてしまいます。なので大きな桁数を格納する事ができるDouble型を採用しています。また、この電卓アプリには割り算も計算できるようにしているため、計算結果として小数が含まれる可能性があるのも理由です。

次に今回の電卓では各ボタンがタップされたら下記の処理を実行するようにして電卓機能を実装していきます。

条件 処理内容
数字ボタン(1〜9)をタップした場合 ・numberの変数に10を掛けて入力された数字を加算計算
・計算結果表示ラベルにnumberの値を表示
演算子ボタン(+,-,÷,×)をタップした場合 ・operatorの変数にタップされた演算子のタイトルを代入、またresult変数にnumberの数字を代入
・number変数は0にリセット
等号(=)ボタンをタップした場合 ・result変数とnumber変数とをoperator変数の演算子をもとに計算し、結果をresult変数に代入
・計算結果表示ラベルに表示

それでは具体的な処理を記述するまえに、上記の条件で処理を分けるために条件分岐のプログラムを書きましょう。

ViewController.swift

func buttonTapped(sender:UIButton!){
print("buttonTapped!!)
var tappedButton:UIButton = sender as UIButton
var tappedButtonTitle:NSString = tappedButton.currentTitle!
print("tappedButtonTitle:\(tappedButtonTitle)")
///////////// ▼追加▼ /////////////
// ボタンのタイトルで条件分岐
switch tappedButtonTitle {
// 数字をタップした場合
case "0","1","2","3","4","5","6","7","8","9":
self.numberButtonTapped(nbt: tappedButtonTitle)
case "×","-","+","÷":
self.operatorButtonTapped(obt: tappedButtonTitle)
case "=":
self.equalButtonTapped(ebt: tappedButtonTitle)
case "C":
self.clearButtonTapped(cbt: tappedButtonTitle)
default:
print("その他")
}
///////////// ▲追加▲ /////////////
}
///////////// ▼追加▼ /////////////
// 数字のボタンをタップした時
func numberButtonTapped(nbt tappedButtonTitle:NSString){
print("数字ボタンタップ:" + (tappedButtonTitle as String))
}

// 演算子のボタンをタップした時
func operatorButtonTapped(obt tappedButtonTitle:NSString){
print("演算子ボタンタップ:" + (tappedButtonTitle as String))
}

// 等号のボタンをタップした時
func equalButtonTapped(ebt tappedButtonTitle:NSString){
print("等号ボタンタップ:" + (tappedButtonTitle as String))
}

// クリアのボタンをタップした時
func clearButtonTapped(cbt tappedButtonTitle:NSString){
print("クリアボタンタップ:" + (tappedButtonTitle as String))
}
///////////// ▲追加▲ /////////////

それではこの状態でビルドしてiOSシュミレーターで確認してみましょう。
数字ボタン、演算子ボタン、等号ボタン、クリアボタンをタップした時で実行しているメソッドが変わっている事を確認しましょう。

数字「1」〜「9」ボタンをタップした場合

数字のボタン(「1」から「9」のボタン)をタップしたときの処理を入れていきます。

ViewController.swift

// 数字のボタンをタップした時
func numberButtonTapped(tappedButtonTitle:NSString){
print("数字ボタンタップ:\(tappedButtonTitle)")
///////////// ▼追加▼ /////////////
// タップされた数字タイトルを計算できるように変換
let tappedButtonNum:Double = tappedButtonTitle.doubleValue
// 入力されていた値を10倍にして1桁大きくして、その変換した数値を加算
number = number * 10 + tappedButtonNum
// 計算結果ラベルに表示
resultLabel.text = NSString(format: "%.0f", number) as String
///////////// ▲追加▲ /////////////
}

ここではタップされた数字のボタンのタイトルが引数にセットされていますので、その文字をDouble型に変換しています。

// タップされた数字タイトルを計算できるように変換
let tappedButtonNum:Double = tappedButtonTitle.doubleValue

そして、今まで入力されていた値(numberの値)を10倍にして1桁大きくして、その変換した数値を足しています。

// 入力されていた値を10倍にして1桁大きくして、その変換した数値を加算
number = number * 10 + tappedButtonNum

最後にその計算結果を計算結果ラベルに表示させています。

// 結果表示ラベルに表示
resultLabel.text = NSString(format: "%.0f", number) as String

演算子「+」「-」「÷」「×」ボタンをタップした場合

演算子(「+」「-」「÷」「×」)のボタンをタップした場合は下記はメンバ変数に定義したoperationIdにタップされた演算子情報を格納しておき、今まで入力された数値を計算結果格納用の変数(result)に格納し、入力用変数(number)は0にリセットしています。

ViewController.swift

// 演算子のボタンをタップした時
func operatorButtonTapped(tappedButtonTitle:NSString){
print("演算子ボタンタップ:" + (tappedButtonTitle as String))
///////////// ▼追加▼ /////////////
operatorId = tappedButtonTitle as String
result = number
number = 0
///////////// ▲追加▲ /////////////
}

等号「=」ボタンをタップした場合

等号ボタンをタップした場合はさらにswitch-case文を使い、operatorIdに格納されている文字列で処理を分けています。それぞれの演算子に会わせて四則演算しています。

ViewController.swift

// 等号のボタンをタップした時
func equalButtonTapped(tappedButtonTitle:NSString){
print("等号ボタンタップ:\(tappedButtonTitle)")
///////////// ▼追加▼ /////////////
switch operatorId {
case "+":
result += number
case "-":
result -= number
case "×":
result *= number
case "÷":
result /= number
default:
print("その他")
}
number = result
resultLabel.text = NSString(format: "%.0f", result) as String
///////////// ▲追加▲ /////////////
}

クリア「C」ボタンをタップした場合

クリア(「C」)ボタンをタップした場合の処理は入力値、計算結果及び演算子格納用の変数をリセットしています。リセット後に計算結果ラベルの表示を更新しています。

ViewController.swift

// クリアのボタンをタップした時
func clearButtonTapped(tappedButtonTitle:NSString){
print("クリアボタンタップ:\(tappedButtonTitle)")
///////////// ▼追加▼ /////////////
number = 0
result = 0
operatorId = ""
resultLabel.text = NSString(format: "%d", number) as String
///////////// ▲追加▲ /////////////
}

動作確認

それでは電卓の機能機能を実装しました。ビルドして動作確認してみましょう。
ちゃんと電卓として計算結果はあっているでしょうか。確認してみてください。

計算結果の誤差を解消するには

実は今までDouble型で計算をしましたが、大きな値や小数の計算をすると計算結果に誤差が生じます。

正確に計算するにはDouble型のデータで計算するのではなく、NSDecimalNumberで計算をすれば正確に計算できます。修正してみましょう。

まずはメンバ変数のnumberとresultをDouble型からNSDecimalNumber型に変更します。

ViewController.swift

// 入力数字
var number:NSDecimalNumber = 0

// 計算結果
var result:NSDecimalNumber = 0

次に数字タップ時の処理も次のように変更します。

ViewController.swift

// 数字のボタンをタップした時
func numberButtonTapped(nbt tappedButtonTitle:NSString){
print("数字ボタンタップ:" + (tappedButtonTitle as String))
///////////// ▼修正▼ /////////////
let tappedButtonNum:NSDecimalNumber = NSDecimalNumber(string: tappedButtonTitle as String)
number = number.multiplying(by: NSDecimalNumber(string: "10")).adding(tappedButtonNum)
resultLabel.text = number.stringValue
///////////// ▲修正▲ /////////////
}

演算子タップ時の処理も変更します。numberを0にリセットしている処理をNSDecimalNumber型に変更しています。

ViewController.swift

// 演算子のボタンをタップした時
func operatorButtonTapped(obt tappedButtonTitle:NSString){
print("演算子ボタンタップ:" + (tappedButtonTitle as String))
///////////// ▼修正▼ /////////////
operatorId = tappedButtonTitle as String
result = number
number = NSDecimalNumber(string: "0")
///////////// ▲修正▲ /////////////
}

等号ボタンタップ時の処理も次のように変更します。

ViewController.swift

// 等号のボタンをタップした時
func equalButtonTapped(ebt tappedButtonTitle:NSString){
print("等号ボタンタップ:" + (tappedButtonTitle as String))
///////////// ▼修正▼ /////////////
switch operatorId {
case "+":
result = result.adding(number)
case "-":
result = result.subtracting(number)
case "×":
result = result.multiplying(by: number)
case "÷":
if(number.isEqual(to: 0)){
number = 0
resultLabel.text = "無限大"
return
} else {
result = result.dividing(by: number)
}
default:
print("その他")
}
number = 0
resultLabel.text = result.stringValue
operatorId = "="
///////////// ▲修正▲ /////////////
}

最後にクリアボタンタップ時の処理も変更してください。

// クリアのボタンをタップした時
func clearButtonTapped(tappedButtonTitle:NSString){
    print("クリアボタンタップ:\(tappedButtonTitle)")
    ///////////// ▼修正▼ /////////////
    number = NSDecimalNumber(string: "0")
    result = NSDecimalNumber(string: "0")
    operatorId = ""
    resultLabel.text = number.stringValue
    ///////////// ▲修正▲ /////////////
}

これで修正は完了しました。
大きな数字で計算してみてください。正確に計算できていれば完成です。

仕上げに見た目を調整しよう

仕上げとして、電卓アプリの見栄えを整えていきましょう。

ボタンの背景色を変更

まず、カラーをHEX値で指定できるように下記のメソッドを定義します。

ViewController.swift

///////////// ▼追加▼ /////////////
// カラーをHEX値で指定
func colorWithRGBHex(hex: Int, alpha: Float = 1.0) -> UIColor {
let r = Float((hex >> 16) & 0xFF)
let g = Float((hex >> 8) & 0xFF)
let b = Float((hex) & 0xFF)
return UIColor(red: CGFloat(r / 255.0), green: CGFloat(g / 255.0), blue:CGFloat(b / 255.0), alpha: CGFloat(alpha))
}
///////////// ▲追加▲ /////////////

HEX値とはhexadecimal numberの略で16進数の事をいい、0から9までの10個の数字と、AからFまでの6個のアルファベットを使って数値を表現する方法です。カラー情報はHEX値で表現している事が多く、様々な色を指定したい場合に便利です。今回もボタンや計算結果エリアのカラー指定はHEX値を指定していきます。

ボタンにグラデーションを掛ける

ボタンの色を緑色からグラデーションをかけた立体的なものに変更します。
まず、グラデーションをつけるのには、QuartzCore.frameworkが必要です。QuartzCore.frameworkとはView要素のグラフィクスやアニメーションを実装するためのフレームワークです。

QuartzCore.frameworkをlink binary widh Libraryに追加したら下記のようにコードを追加してください。

ViewController.swift

import QuartzCore // 追加

class ViewController: UIViewController {
:
:
override func viewDidLoad() {
:
:
// 繰り返し処理でボタンを配置
for y in 0 ..< yButtonCount {
for x in 0 ..< xButtonCount {
:
:
// ボタン背景色設定
button.backgroundColor = UIColor.green
///////////// ▼追加▼ /////////////
let gradient : CAGradientLayer = CAGradientLayer()
gradient.frame = button.bounds
let arrayColors: NSArray = [
self.colorWithRGBHex(hex: 0xFFFFFF, alpha: 1.0).cgColor as AnyObject,
self.colorWithRGBHex(hex: 0xCCCCCC, alpha: 1.0).cgColor as AnyObject
]
gradient.colors = arrayColors as? [Any]
button.layer.insertSublayer(gradient, at: 0)
// 角丸
button.layer.masksToBounds = true
button.layer.cornerRadius = 5.0
// ボタンのテキストカラー
button.setTitleColor(UIColor.black, for: .normal)
button.setTitleColor(UIColor.gray, for: .highlighted)
///////////// ▲追加▲ /////////////

追加したらビルドして確認してみましょう。下記のようにボタンにグラデーションがついて立体的になったと思います。

フォントを変更

計算結果を表示する箇所のフォントをデジタル風に変更します。iOSアプリはフォントを追加して利用する事ができます。

フォントファイルは様々なサイトで配布されています。今回は商用フリーで利用できるフォントをダウンロードしました。

http://www.fontspace.com/glyphobet-font-foundry/letters-laughing

フォントファイルをダウンロードしたらプロジェクト内に取り込みます。FinderからXcodeプロジェクトのナビゲーションエリアにドラックしてインポートしてください。
電卓アプリのプロジェクトに「Fonts」というグループを作成してその中にインポートしています。

フォントファイルのインポートが完了したら、info.plistファイルを開いてFonts provided by applicationの項目を追加し、そのitem0の値に先程ダウンロードしてインポートしたフォントファイル名を指定します。
フォントが複数ある場合はArray要素を追加してItem 1, Item 2, … にフォントファイル名を記述します。

これでフォントを利用できる準備が整いました。プログラムで下記のように指定すればフォントを適用できます。nameで指定しているものはフォントがメタデータとして持っているPostScript名 を指定します。

ViewController.swift

// 画面に配置
self.view.addSubview(resultLabel)
///////////// ▼追加▼ /////////////
//フォント設定
resultLabel.font = UIFont(name:"LettersLaughingattheirExecution",size:50)
///////////// ▲追加▲ /////////////

フォントのPostScript名の確認方法

Macでフォントの情報を調べたいときには、Font Bookを利用します。
フォントファイルをダブルクリックして下記の画面を表示し、「フォントのインストール」をクリックします。そうするとMacにフォントがインストールされます。

インストールしたらFont Bookを起動させるとインストールしたフォントがありますのでそれをクリックするとPostScript名が表示されています。

このようにフォントが変更されました。

画面の背景色を変更

画面の背景色を白から黒に変更します。画面全体の背景色を変更するにはself.viewに対してのbackgroundColorプロパティを変更します。

ViewController.swift

override func viewDidLoad() {
super.viewDidLoad()
///////////// ▼追加▼ /////////////
// 画面全体の背景色
self.view.backgroundColor = UIColor.black
///////////// ▲追加▲ /////////////

結果表示エリアの背景色を変更

結果表示エリアの背景色を変更します。灰色だとちょっと暗いので明るい色に変更しています。
ここでは先程定義したHEX値による色指定をしています。

ViewController.swift

///////////// ▼修正▼ /////////////
// 計算結果ラベルの背景色を灰色にする
//resultLabel.backgroundColor = UIColor.grayColor()
resultLabel.backgroundColor = self.colorWithRGBHex(hex: 0xF5F5DC, alpha: 1.0)
///////////// ▲修正▲ /////////////

ステータスバーのカラーを変更

背景を黒くしたら画面上部にあるステータスバーが見えなくなってしまいました。ステータスバーを表示するようにします。

info.plistを開き、「View controller-based status bar appearance」を追加しValue値をNOにします。

次にAppDelegate.swiftファイルを開き、下記のように記述します。

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
///////////// ▼追加▼ /////////////
// ステータスバーのテキストカラー
UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent
///////////// ▲追加▲ /////////////
return true
}

完成

これでシンプル電卓アプリは完成です。このシンプル電卓アプリは最低限のボタンしかありませんので、色々ボタンを追加して高機能な電卓アプリにしてみてください。