エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

Swift実践入門 〜 今からはじめるiOSアプリ開発! 基本文法を押さえて、簡単な電卓を作ってみよう

初めてSwiftを触るエンジニアの方向けに、Swiftの基本から、実際に動くiOSアプリ開発までを解説します。実際に手を動かして覚える内容となっているので、Xcodeを操作しながら読み進んでください。

Swift実践入門

はじめまして、山野貴史と申します。Webアプリケーション開発やiOSアプリの制作等を行っています。

好物はRubyと、Rails。RailsAmpという、AMP(Accelerated Mobile Pages) に対応するRuby on Railsのプラグインgemを作りました。エディタはVim派です。Swiftも大好きです。

本稿では、初めてSwiftを触るエンジニアの方向けに、Swiftの基本から、実際に動くiOSアプリ開発までを解説します。Swiftを対話的に動作させて慣れた後、簡単な電卓アプリを制作してみます。

SwiftのサンプルコードやXcodeのスクリーンショットを豊富に掲載し、手を動かして覚える内容となっています。読者の皆様自身がXcodeの操作を通じて、実際の動作を確認しながら読み進んでいただけると嬉しく思います!

Swiftの特徴

Swiftは、Apple社のiOSやmacOSのアプリケーション開発に用いられるプログラミング言語です。本稿の執筆時点で、最新バージョンはSwift 3.1です。

Swiftの主な特徴は以下の2点です。

静的型付け
プログラム実行前の段階(コンパイル時)に、変数や定数の型情報を決定する。実行時エラーを事前に発見できる
型推論
変数や型を宣言しなくても、変数の型が自動的に決定される。記述が簡潔になる

また、Swiftはオブジェクト指向や関数型言語の特徴を持っています。WebエンジニアでRubyやPython等の経験があれば、Swiftの習得も早いはずです。オブジェクト指向やクロージャの概念が、Swift開発でも役に立ちます!

型付け 処理系 IDE/エディタ 主な開発分野
Swift 静的 コンパイラ Xcode iOS、macOS、tvOS
Java 静的 コンパイラ Eclipse、NetBeans、IntelliJ IDEA、Android Studio等 Webサーバーサイド、Android、各種OS・組み込みシステム等幅広い
Ruby 動的 インタプリタ(JRuby等の処理系ではコンパイラ可) Vim、Emacs、Atom、RubyMine等 Webサーバーサイド(Ruby on Rails)
Python 動的 インタプリタ Vim、Emacs、Atom、PyCharm等 Webサーバーサイド、機械学習、統計分析等

ひとつ注意点ですが、Swiftは書き方の自由度が比較的高いので、チーム開発をする場合は、あらかじめコーディング規約を共有しておいた方が良いでしょう。

Swiftでの命名や慣例のほか、さまざまな指針がSwift.orgにある「API Design Guidelines」にまとめられています。興味のある方は目を通してみてください。

Swift.org - API Design Guidelines

インタラクティブ環境でSwiftを触ってみよう

早速ですが、Swiftを触って動作させてみましょう。Swiftはコンパイラ言語でありながら、インタラクティブ(対話的)に実行しながら動作を確認することができます。Swiftに慣れるには、このインタラクティブ環境でいろいろと動作させてみるのが手っ取り早いです。

インタラクティブにSwiftを実行するには、ターミナル(コマンドライン)のREPL(レプル)を使う方法と、XcodeのPlaygroundを使う方法の2つがあります。

動作確認の環境

本記事中で、サンプルコードの動作確認を行った環境のバージョン情報です。

  • MacOS Sierra 10.12.4
  • Swift 3.1
  • Xcode 8.3.2
  • CocoaPods 1.2.1
  • Expression 0.5.0(CocoaPodsライブラリ)
  • iOSシミュレーター iPhone 7&iOS 10.3

Swift、Xcodeのバージョンは、以下コマンドで確認できます。

$ swift --version
Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42)
Target: x86_64-apple-macosx10.9

$ xcodebuild -version
Xcode 8.3.2
Build version 8E2002

それでは、Swiftの実践へと移ります!

実行方法1. ターミナルのREPL

REPLとは、Read-Eval-Print-Loop(対話型実行環境)のことです。Swiftは、ターミナルからREPLを起動できます。

$ swift
Welcome to Apple Swift version 3.1 ...
  1> print("hello world")
hello world
  2> 1 + 1
$R0: Int = 2
  3> :exit
$

ターミナルでswiftと入力すると、矢印が表示され、入力待ちの状態となります。Swiftのコードを書いて、returnキーを押します。Swiftコードが実行され、コードが評価された結果が次の行に出力されます。REPLは、:exitで終了できます。

実行方法2. XcodeのPlayground

Xcodeは、エディタのほか、デバッガやGUIデザイナ等の機能を備えた、SwiftのIDE(統合開発環境)です。XcodeのPlaygroundを使うと、Swiftを対話的に実行できます。 通常、SwiftでのiOSアプリ開発にはXcodeを使い、ビルドして実行します。

Xcodeは、Mac App Storeからインストールしましょう。

Xcode - Apple Developer

Xcodeを起動して、「Get started with a playground」を選択します。

または、Xcodeのメニューから[File]→[New]→[Playground…]を選択します。

ターミナルのREPLと同様に、PlaygroundでもSwiftを対話的に実行できます。Playground上で実行したコードが保存できますので、対話的に書いたコードを後で見直したい場合にも便利です。

Vimmerならプラグイン「XVim」をインストールしよう

唐突ですが、読者の皆さんはVim好きですか? 私はVimが大好きです! RubyやRails等でコードを書くときはもちろん、この記事執筆にも、いつでもどこでもVimを使っています。

Vim使いの方であれば、当然「XcodeもVimのキーバインドで使いたい」って考えますよね。そんなVimmer達の願いをかなえてくれるのが、XVimというXcodeの神プラグインです。Vim好きな方は、iOS開発に取り組む前に、ぜひXVimをインストールしてみてください。

github.com

Xcode 8の場合は、XVimインストールの前に、あらかじめXVimプラグイン用の証明書を作成しておく必要があります。証明書の作成方法は次のドキュメントをご参照ください。

Install XVim for Xcode 8 - XVimProject/XVim

証明書を作成した後は、以下の手順でXVimをインストールします。

$ cd ~/Library/Application\ Support/Xcode
$ mkdir plugins
$ cd plugins
$ git clone https://github.com/XVimProject/XVim
$ cd XVim
$ make
...
 ** BUILD SUCCEEDED **

XVimは、Playgroundでも動作します。Vimmerな方はあらかじめインストールしておくと、Playground上でコードを書いて試すのが断然速くなります。

Emacsのキーバインド

XcodeをEmacsのキーバインドで使いたい場合、本稿執筆現在ではKarabiner正式版の最新バージョンがmacOS Sierraに対応していないため、代替としてHammerspoonを使うと良いようです。

Swiftの基本文法(1)変数、配列、ディクショナリの定義、制御構造

アプリ制作に入る前に、Swiftの基本的な文法を確認していきます。

Swiftの基本的な文法は、CやC++、Objective-C、Java、Ruby、Python、JavaScript等のプログラミング言語を一つでも知っている方であれば、比較的なじみやすいでしょう。

Swiftには、BoolIntFloatDoubleCharacterStringArrayDictionary等の基本的な型に加えて、nil(他言語ではnullとも呼ばれる)を許容するOptionalという型が存在します。Swiftの特徴であるOptional型や、それを扱うOptional Bindingと呼ばれる機能については、初見ですとちょっと難しいので後述します。

本稿のサンプルコードは、先述したターミナルのREPLか、XcodeのPlaygroundで試しながら読み進めると理解しやすいかと思います。

変数の定義

まずは変数の定義です。変数の定義にはletvarを使います。

// letでイミュータブルな変数(1行コメント)
let immutableStr = "hello"
immutableStr += " world"  // => error

/*
 * varでミュータブルな変数
 * (複数行コメント)
 */
var mutableStr = "hello"
mutableStr += " world"
print(mutableStr)  // => hello world

※イミュータブル……作成後に状態を変えられないオブジェクトのこと。対義語はミュータブル

上の例では、型推論により、変数の型はString型となります。明示的に型を指定するには、以下のように書きます。

var mutableStr: String = "hello"

Swiftでは 暗黙の型変換は行われません。よって、型を変換したい場合は、明示的に記述する必要があります。

let label = "This year is "
let year = 2017
let thisYear = label + String(2017)
print(thisYear)  // => This year is 2017
// 文字列内での変数展開
let nextYear = "Next year is \(year + 1)"
print(nextYear)  // => Next year is 2018

配列の定義

配列(Array型)は次のように定義します。

var itemsArray = ["foo", "bar", "baz"]
// 型を明示
var itemsArray: [String] = ["foo", "bar", "baz"]

ディクショナリの定義

ディクショナリ(Dictionary型)は、["キー": "値"]のように記します。

var itemsDictionary = [
  "foo": "FOO",
  "bar": "BAR",
  "baz": "BAZ"
]

// 型を明示
var itemsDictionary: [String: String] = [
  "foo": "FOO",
  "bar": "BAR",
  "baz": "BAZ"
]

制御構造

forループや、ifelseによる制御は次のように記述します。

let list = [3, 7, 9, 12, 8, 5]
for number in list {
  if number % 2 == 0 {
    print("number \(number) is even")
  } else {
    print("number \(number) is odd")
  }
}

whileループです。

var number = 1
while number < 10 {
  print(number)
  number += 1
}

switchcaseです。

let age = 17
switch age {
case 0...6:
  print("kindergarden kid")
case 7...12:
  print("primary school student")
case 13...15:
  print("junior high school student")
case 16...18:
  print("high school student")
case 19...22:
  print("college student")
default:
  print("business person")
}
// => high school student

Swiftでは、RubyのようなRangeオブジェクトを使うことができ、さらにswitchcaseでは、数値がRangeオブジェクトの範囲に入るかどうかを良い感じに判定してくれます。

ここまではC言語由来のプログラミング言語の文法に似ていますので、分かりやすいです。

Swiftの基本文法(2)関数、クロージャ、クラス定義・オブジェクト

関数(メソッド)

関数(メソッド)定義の書き方です。

func greet(expression: String, person: String) -> String {
  return "\(expression) \(person)."
}

greet(expression: "Hello", person: "Mike")  // => "Hello Mike."

以下の書き方が、関数定義の基本になります。

func 関数名(引数1: 引数1の型, 引数2: 引数2の型, ...) -> 返り値の型 {
}

関数を呼び出すときは、名前付き引数として(引数ラベル: 引数の値...)のように渡します。

関数定義で引数ラベルを明示する場合は、以下のように書きます。アンダースコアを使うと、関数呼出し時に名前付き引数のラベルを省略できます。

func greet(_ expression: String, to person: String) -> String {
  return "\(expression) \(person)."
}

greet("Hello", to: "Mike")  // => "Hello Mike."

なお、Swiftでは関数のシグネチャの書き方が多種多様です。見慣れない書き方があった場合は、その都度チェックしてみてください。

クロージャ

続いて、Swiftでのクロージャの例を見てみましょう。JavaScript等で同様の例をご存知の方も多いのではないでしょうか。

func incrementer() -> ( () -> Int ) {
  var count = 0
  func increment() -> Int {
    count += 1
    return count
  }
  return increment
}

var counter = incrementer()
counter()  // => 1
counter()  // => 2
counter()  // => 3

returnされる関数を無名関数にしてみます。

func incrementerWithAnonymousFunc() -> ( () -> Int ) {
  var count = 0
  return { () -> Int in
    count += 1
    return count
  }
}

var counter2 = incrementerWithAnonymousFunc()
counter2()  // => 1
counter2()  // => 2
counter2()  // => 3

関数は、引数として関数を受け取ることができます。

func numbersMap(list: [Int], condition: (Int) -> Int) -> [Int] {
  var numbers: [Int] = []
  for item in list {
    numbers.append( condition(item) )
  }
  return numbers
}

var items: [Int] = [1, 2, 3, 4, 5]
numbersMap(list: items, condition: { (number: Int) -> Int in number * 2 })  // => [2, 4, 6, 8, 10]

このクロージャ機能により、Swiftではmapfilterreduce等のメソッドに、引数として無名関数のブロックコードを渡して処理することができます。

var numbers = [3, 7, 9, 12, 8, 5]
// 配列の要素をすべて2倍にする
numbers.map({ (number: Int) -> Int in return number * 2 })  // => [6, 14, 18, 24, 16, 10]

// 奇数のみを抽出する
numbers.filter({ (number: Int) -> Bool in return number % 2 == 1 })  // => [3, 7, 9, 5]

// すべての合計を計算する
numbers.reduce(0, { (total: Int, number: Int) -> Int in
  return total + number
})  // => 44

さらに、mapfilterreduceの引数でブロックコードを渡す場合は、型指定を省略した書き方が可能です。

numbers.map{ number in number * 2 }  // => [6, 14, 18, 24, 16, 10]
numbers.filter{ number in number % 2 == 1 }  // => [3, 7, 9, 5]
numbers.reduce(0){ total, number in total + number }  // => 44

まるでRubyのコードのようですね。Rubyのブロック引数の||inに変わっただけで、とてもよく似ています。SwiftがRubyに影響を受けたことを伺わせるシンタックスです。

クラス定義とオブジェクト

次は、クラス定義とオブジェクト作成のコード例です。他言語と似たような書き方ですので、分かりやすいかと思います。

class MyApp {
  // Shapeクラスの定義
  class Shape {
    // nameプロパティ(インスタンス変数)
    var name: String

    // イニシャライザ(コンストラクタ)
    init(name: String) {
      self.name = name
    }
  }

  // 四角形: RectangleがShapeを継承
  class Rectangle: Shape {
    var width: Double
    var height: Double

    init(name: String, width: Double, height: Double) {
      self.width = width
      self.height = height
      // 親クラスのイニシャライザ呼び出し
      super.init(name: name)
    }

    func area() -> Double {
      return width * height
    }
  }

  // 三角形: TriangleがShapeを継承
  class Triangle: Shape {
    var bottom: Double
    var height: Double

    init(name: String, bottom: Double, height: Double) {
      self.bottom = bottom
      self.height = height
      super.init(name: name)
    }

    func area() -> Double {
      return bottom * height / 2.0
    }
  }
}

// 正方形を作成
var square = MyApp.Rectangle(name: "My Square", width: 7.5, height: 7.5)
square.name    // => "My Square"
square.area()  // => 56.25

// 三角形を作成
var triangle = MyApp.Triangle(name: "My Triangle", bottom: 10, height: 8)
triangle.name    // => "My Triangle"
triangle.area()  // => 40

そのほか、Swiftでは、enum(列挙型)struct(構造体)protocol(インターフェース)extension(クラス拡張、モンキーパッチ)generics(ジェネリクス)機能も利用できます。ここで詳しくは触れませんが、興味がある方はドキュメント等を参照してください。

Swiftの基本文法(3)Optional型

Swiftの言語仕様の特徴として、nilを安全に扱うためにOptionalという型が存在します。

そのため、Swiftでnilを許容する変数は、Optional型として定義する必要があります。変数をOptional型で定義しない限り、変数にnilを代入することはできません。非Optional型の変数、たとえば通常のInt型やString型の変数には、nilを代入できません。

Optional型を使うことで、変数やメソッドの戻り値がnilになり得ることを、プログラマが明示できます。不適切にnilの可能性のある変数にアクセスするコードを書いていた場合、コンパイル時に警告が出て、nilが返ることによる予期せぬ実行時エラーの発生を防ぐ働きをしてくれるわけです。

Ruby/Railsでは、次の例のようにメソッド呼び出しやメソッドチェインの途中でnilが返ってしまい、NoMethodErrorの実行時エラーが発生した経験はないでしょうか?

user.name                   # userがnilの場合NoMethodErrorになる
user.name if user.present?  # ifでチェックしuserが存在する場合のみ実行
user&.name                  # safe navigation operator -> userがnilの場合nilを返す
user.try(:name)             # RailsのObject#try -> userがnilの場合nilを返す

Ruby/Railsの場合、変数のnilチェックやnilアクセスに対する適切な処理は、プログラマのコーディングに委ねられています。上記の例のように、ifnilチェックしたり、tryメソッドやsafe navigation operator(通称ぼっち演算子&.を使うといった処理を、プログラマが責任を持って行う必要があります。

一方、Swiftでは、nilになり得る変数を使う場合のために、言語仕様自体にnilを安全に扱えるようにするOptional型という仕組みが組み込まれています。

また、SwiftでOptional型を扱う際には、さまざまな文脈で?(クエスチョンマーク)!(エクスクラメーションマーク)が登場します。Rubyを使われる方は、Rubyでは真偽値を返すメソッドに?が付いたり、破壊的メソッドに!が付くことをご存知かと思いますが、Swiftではまったく概念が異なりますので、その点は注意してください。

Optional型の実際のコードを見ていきましょう。

Optional型の変数を定義

// Optional Int
var optionalInt: Int? = 5
// 通常のInt
var int: Int = 5

// Optional String
var optionalStr: String? = "hello"
// 通常のString
var str: String = "hello"

// Optional Stringの変数にnilを代入
optionalStr = nil
// 通常のStringにはnilを代入できない
str = nil  // => error: nil cannot be assigned to type 'String'

Int?nilまたはInt型の値を代入できる型、String?nilまたはString型の値を代入できる型です。Int?Intはまったく別の型であり、同様にString?Stringもまったく別の型です。

このT?という書き方は、Optional<T>のシンタックスシュガーですので、以下のようにも書けます。

var optionalInt: Optional<Int> = 5
var optionalStr: Optional<String> = "hello"

Swiftの基本文法(4)Optional型をアンラップする4つの方法

Swiftでは、Optional型の変数を ラップ(wrap)されていると表現します。nilを許容する型でラップされている(包まれている)というイメージでしょうか。

一方、値を扱えるようにするための処理を、アンラップ(unwrap)すると表現します。

アンラップせずに、Optional型の変数に対してメソッドを呼び出そうとすると、変数がアンラップされていないよ! とエラーが発生します。

var optionalStr: String? = "hello"
optionalStr.uppercased()  // => error: value of optional type 'String?' not unwrapped

Optional型の変数をアンラップ処理するには、4つの方法があります。

  1. Optional Binding
  2. Optional Chaining
  3. Forced Unwrapping
  4. ImplicitlyUnwrappedOptional型

方法1. Optional Binding

Optional Bindingは、Optional型の変数を安全に扱うための代表的な方法です。

ifguardを使って、Optional型の変数がnilでないことを確認してアンラップを行います。guardは関数の中でしか使えませんので、以下の2つのコード例はfuncの中で書いています。

まずは、ifを使ったOptional Bindingのコード例です。

func hello() -> String {
  let optionalStr: String? = "hello"
  if let unwrappedStr = optionalStr {
    return unwrappedStr.uppercased()
  }
  return ""
}

print( hello() )  // => HELLO

次に、guardを使ったOptional Bindingのコード例です。

func helloWithGuard() -> String {
  let optionalStr: String? = "hello"
  guard let unwrappedStr = optionalStr else {
    return ""
  }
  return unwrappedStr.uppercased()
}

print( helloWithGuard() )  // => HELLO

guardは、いわゆるガード節の働きをします。Optional型の変数がnilだった場合の処理を、else内に書きます。Optional型の変数がアンラップできた場合は、以降の行でアンラップ済みの変数を使えます。

これらのOptional Bindingにより、Optional型のnilになり得る)変数を安全に扱えます。

方法2. Optional Chaining

Optional Chainingは、アンラップした変数の型のメソッドを呼び出したい場合に使う方法です。

Optinal型の変数の後ろに?.を付けて、メソッド呼び出しを書きます。変数がnilでない場合はメソッド呼び出しの結果を返し、nilの場合はnilを返します。Railsのtryや、Rubyのsafe navigation operator(&.)と似た働きをします。

Optional型の変数を定義する?とは、意味合いがまったく違う点に注意です。

var optionalStr: String?
optionalStr?.uppercased()  // => nil
optionalStr = "hello"
optionalStr?.uppercased()  // => "HELLO"

Optional Chainingを使うと、以下のように?.でつないで安全にメソッドチェインを書くことができます。

if let userName = user?.profile?.name {
  print(userName)
}

userまたはprofileまたはnamenilを返した場合は、user?.profile?.nameの結果がnilを返しますので、Optional Bindingのifブロック内のprint(userName)は実行されません。

方法3. Forced Unwrapping

Forced Unwrappingとは、文字通り強制的にアンラップを行う方法です。T?型の変数の後ろに!を付けることで変数を強制的にアンラップし、T型として扱えます。

var optionalStr: String? = "hello"
optionalStr!.uppercased()  // => HELLO

ただし、このForced Unwrappingを使う方法は、危険であることを認識しなければなりません。Optional型の変数がnilだった場合にForced Unwrappingしてしまうと、ランタイムエラーが起こる(アプリの場合だったら落ちる)可能性があります。変数の中身が絶対にnilではない場合のみに、Forced Unwrappingを使うようにします。

Swiftでは、!は注意すべきマークと意識してしておくと良いでしょう。!を使う場合は、絶対に処理が失敗しないようにする必要があります。

方法4. ImplicitlyUnwrappedOptional型

nilが代入可能な変数を定義する型として、ImplicitlyUnwrappedOptional型があります。

var implicitlyStr: String!  // nil
implicitlyStr = "hello"     // "hello"
implicitlyStr.uppercased()  // => "HELLO"

ImplicitlyUnwrappedOptional型の変数は、メソッド呼び出しの際に、通常の型と同じような呼び出し方で書けます。

ただし、Forced Unwrappingと同様に、ImplicitlyUnwrappedOptional型の変数がnilの場合、実行時エラーになる可能性がある点に注意です。絶対に変数がnilになり得ない場合にのみ、ImplicitlyUnwrappedOptional型を使うようにします。

Optional型の変数を扱う際には、上述した4つの方法を用いて適切に処理するようにします。

ここまでで、Swiftの基本文法は終了です。すべてを網羅しているわけではありませんので、詳細は公式ドキュメントや書籍をご確認ください。

電卓アプリを作ってみよう!

Swiftの特徴から基本文法までを、駆け足で見てきました。最後のまとめとして、簡単な電卓アプリを作ってみましょう。ここまでに紹介したSwift文法の知識があり、もう少しだけXcodeの操作法を学べば、実際に動くアプリを作ることができます。

制作する電卓アプリのUIと仕様

ここでは、次のようなアプリを制作します。

  • 入力された計算式を表示する
  • 答えを表示する
  • 数字、演算子(+、ー、×、÷)、小数点、カッコを入力でき、それらを使った四則演算ができるようにする
  • (イコール)を押したら、計算式を評価して答えを表示
  • C(クリア)ボタンを押したら、計算式と答えをクリアする
  • 入力された計算式が不当な場合、「式を正しく入力してください」と表示する

この電卓アプリのUIと仕様を考えたときの手書きのノートです(字が汚くてすみません)。こんな感じでざっと紙に書いてみると考えがまとまりやすいです。もちろんソフトウェアのツールを用いてもかまいません。

仕様を考えた後は、作業手順用のToDoリストを書き出すと良いでしょう。

作業手順(ToDoリスト)

  • 必要なライブラリをCocoaPodsでインストール(今回はExpressionというライブラリを使います)
  • View Controllerにラベル(UILabel)を配置( 式用、=用、答え用 )
  • View Controllerにボタン(UIButton)を配置( 0123456789.÷x-+=()C )
  • ラベルのOutlet接続
  • ボタンのAction接続
  • ボタンが押されたら式用のラベルに式の文字列を表示・更新
  • =ボタンが押されたら答え用のラベルに答えを表示
  • Cボタンが押されたら式と答えをクリア

それでは電卓アプリを作っていきます!

1. Xcodeで新規プロジェクトを作成

Xcodeを起動して「Create a new Xcode project」または、メニューから[File]→[New]→[Project]を選択し、新規プロジェクトを作成します。

次の画面で、[iOS]の[Single View Application]を選択して[Next]で進みます。

次の画面で、以下のように入力して[Next]で進みます。[Organization Name]は任意の名前でかまいません。

[Organization Identifier]には、持っているドメイン等を入力します。これはアプリの識別IDの一部となるため重要で、必須入力です。通常は、所有しているドメイン名を逆順にして入力します。

次に、プロジェクトを保存する任意のフォルダを選択します。[Create Git repository on]のチェックを外して[Create]します。

ここで、Xcodeをいったん終了させます。

コンソールで.gitignoreを作成後、Git管理下にします。ここでは、プロジェクトを保存したフォルダをCalculatorAppとします。

$ cd /path/to/XcodeProject/CalculatorApp
$ vi .gitignore
$ git init
$ git add .
$ git commit -m "initial commit"

SwiftとXcodeで開発するときの.gitignoreは、次のドキュメントが参考になります。

.gitignore for Swift / originally based on gibo swift and added some other settings.

以降は、作業を進めながら適宜gitコマンドでコミットしてください。

2. Expressionをインストール

続いて、Swiftのライブラリ「Expression」をインストールします。ここでは、電卓に入力された文字列をSwiftコードとして評価するために使います。

ライブラリの管理にはCocoaPods.orgを利用します。CocoaPodsは、RubyでいうBundlerのようなツールです。使用するライブラリをPodfileGemfileに相当)に書いて、インストールします。

CocoaPodsのインストール手順は、以下の通りです。pod setupに少々時間がかかります。

$ sudo gem install cocoapods
$ pod setup
$ pod --version
1.2.1

プロジェクトを保存したフォルダに、Podfileを作成します。

$ cd /path/to/XcodeProject/CalculatorApp
$ pod init

Podfileを次のように編集します。

platform :ios, '9.0'

target 'CalculatorApp' do
  use_frameworks!
  pod 'Expression'
end

ライブラリをインストールします。

$ pod install

CalculatorApp.xcworkspaceが作成されるので、これをXcodeプロジェクトとして開きます。

$ open CalculatorApp.xcworkspace

3. View Controllerにラベルとボタンを配置

Storyboardで、部品(オブジェクト)を配置していきます。Storyboardとは、アプリケーションのラベル(UILabel)やボタン(UIButton)等の部品を、GUIによる操作で配置できるXcodeの機能のことです。

プロジェクトを開いたら、Main.storyboardを選択します。

右下のObject Libraryに「UILabel」と入力して、UILabelを選択し、View Controller上にドラッグ&ドロップします。グリッド線が表示されるので、ラベルの幅を調整します。

右端にあるAttributes inspectorで、[Label]の[Text]を編集すると、ラベル名を変更できます。

同様の手順で、UILabelを用いて、=(イコール)用のラベル、答え用のラベルを配置します。

続いて、数字や演算子等のボタンを配置します。右下のObject Libraryで「UIButton」と入力し、UIButtonを選択し、View Controller上にドラッグ&ドロップします。ドラッグ&ドロップを繰り返し、グリッド線を調整して、UIButtonを次のように配置します。

Attributes inspectorの[Title]に、表示する文字を入力します。数字やカッコ、小数点、演算子は半角文字(ASCII)で入力し、そのままSwiftの計算式として評価できるようにします。また[Background]で[Ligth Gray]を選択して、ボタンを灰色にします。

ただし、プログラミングでは乗算に*、除算には/を使いますが、ボタンの表示ではユーザーが使いやすいように、通常の算術演算子である[×](乗算記号)と[÷](除算記号)を表示させましょう。これはプログラム中で置換します。

部品の配置がひと通り終わりました。ここでいったんビルドして、iOSシミュレーターを確認します。上部メニューで動作を確認したい機種を選択し、▶ボタンを押してビルドして、シミュレーターを起動します。今回はiPhone 7&iOS 10.3の環境でビルドしました。

しばらく待つと、以下のようにシミュレーター上に電卓の画面が表示されるはずです。数字や演算子のボタンを押すと、ボタン(UIButton)が押されたアニメーションが動きます。まだ計算を行うためのコードは実装していませんので、式や答えは表示されません。

4. ラベルのOutlet接続

Main.storyboard上に配置したラベルやボタンを、ソースコードから扱えるようにOutletやActionの接続を行います。

Main.storyboardでView Controllerを選択して、Assistant editorを開きます。Assistant editorは右上の○が2つ重なったアイコン(赤枠)です。

次に、Storyboardの「式用のラベル」を選択し、Controlキーを押しながら、Assistant editorで開いたView Controllerのソースコード上(画像の位置)にドラッグ&ドロップします。

[Name]にformulaLabelと入力し、ほかは次の画像の通りに選択して[Connect]をクリックします。

この操作で、View Controllerのソースコードに、以下の行が追加されます。

@IBOutlet weak var formulaLabel: UILabel!

同様の操作を「答え用のラベル」にも繰り返すと、ラベル(UILabel)のOutlet接続が完了します。

Outlet接続とは、Storyboard上のオブジェクト(部品)を、ソースコード上の変数として使えるようにする仕組みです。

ここまでの作業で、View Controllerのソースコード、およびConnections inspectorのOutletsは、以下の画像のようになりました。

5. ボタンのAction接続

今度は、ボタン(UIButton)のAction接続の作成です。

数字の0のボタンをControlを押しながらAssistant editorにドラッグ&ドロップした後、次の画像のように[Connection]はAction、[Name]はinputFormula、[Type]にはUIButtonと入力して、[Connect]します。

この操作で、以下の行が追加されます。このメソッドの中に、ボタンが押されたときに実行する処理を実装していくという仕組みです。

@IBAction func inputFormula(_ sender: UIButton) {
}

同様の手順を繰り返して、[C]ボタンと[=]ボタンを除くすべてのボタンで、Action接続を作成します。[Name]は、すべて同じinputFormulaとします。

このため、Action接続を作成するたびに、ソースコード上に同名のinputFormulaアクションが追加されますが、1つだけ残して後は削除しましょう。[C]と[=]以外のボタンが押されたときは、すべてこのinputFormulaメソッドで処理を行います。

続いて[C]と[=]についてもAction接続を作成しておきます。[C]ボタンはclearCalculation、[=]ボタンはcalculateAnswerという名前にします。

ここまでで、View Controllerのソースコードには、inputFormulacalculateAnswerclearCalculationメソッドが追加されています。Connections inspectorで、[Received Actions]が以下の画像のようになっていれば、OKです。Storyboard上の部品と、ソースコード上の変数やメソッドが接続できました。

ここから先は、ソースコード上で実装を進めていきます。時折ビルドして、動作を確認しながら実装を進めると良いでしょう。

6. ボタンが押されたら式の文字列を表示・更新

ボタンが押されたときに実行する処理を、ソースコードに書いていきます。ViewController.swiftを開いて、ソースコードのviewDidLoadメソッドおよびinputFormulaメソッドを、次のように編集します。

override func viewDidLoad() {
    super.viewDidLoad()
    // ビューがロードされた時点で式と答えのラベルは空にする
    formulaLabel.text = ""
    answerLabel.text = ""
}

@IBAction func inputFormula(_ sender: UIButton) {
    // ボタン(Cと=以外)が押されたら式を表示する
    guard let formulaText = formulaLabel.text else {
        return
    }
    guard let senderedText = sender.titleLabel?.text else {
        return
    }
    formulaLabel.text = formulaText + senderedText
}

inputFormulaメソッドでは、sender.titleLabel?.textで押されたボタンのTitleを取得できます。ボタンが押されるたびに、式を連結して、式用のラベルに表示するという処理です。

7. イコールが押されたら答えを表示し、Cが押されたら式と答えをクリアする

続いて、[=]ボタンが押されたときに計算を実行して答えを表示するcalculateAnswerメソッド、および[C]ボタンが押されたときに式と答えをクリアするclearCalculationメソッドを実装します。

これらのメソッドを実装すると、最終的にViewController.swiftは、以下のようになります。

import UIKit
import Expression

class ViewController: UIViewController {
    @IBOutlet weak var formulaLabel: UILabel!
    @IBOutlet weak var answerLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        // ビューがロードされた時点で式と答えのラベルは空にする
        formulaLabel.text = ""
        answerLabel.text = ""
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func inputFormula(_ sender: UIButton) {
        // ボタン(Cと=以外)が押されたら式を表示する
        guard let formulaText = formulaLabel.text else {
            return
        }
        guard let senderedText = sender.titleLabel?.text else {
            return
        }
        formulaLabel.text = formulaText + senderedText
    }

    @IBAction func clearCalculation(_ sender: UIButton) {
        // Cボタンが押されたら式と答えをクリアする
        formulaLabel.text = ""
        answerLabel.text = ""
    }

    @IBAction func calculateAnswer(_ sender: UIButton) {
        // =ボタンが押されたら答えを計算して表示する
        guard let formulaText = formulaLabel.text else {
            return
        }
        let formula: String = formatFormula(formulaText)
        answerLabel.text = evalFormula(formula)
    }

    private func formatFormula(_ formula: String) -> String {
        // 入力された整数には`.0`を追加して小数として評価する
        // また`÷`を`/`に、`×`を`*`に置換する
        let formattedFormula: String = formula.replacingOccurrences(
                of: "(?<=^|[÷×\\+\\-\\(])([0-9]+)(?=[÷×\\+\\-\\)]|$)",
                with: "$1.0",
                options: NSString.CompareOptions.regularExpression,
                range: nil
            ).replacingOccurrences(of: "÷", with: "/").replacingOccurrences(of: "×", with: "*")
        return formattedFormula
    }

    private func evalFormula(_ formula: String) -> String {
        do {
            // Expressionで文字列の計算式を評価して答えを求める
            let expression = Expression(formula)
            let answer = try expression.evaluate()
            return formatAnswer(String(answer))
        } catch {
            // 計算式が不当だった場合
            return "式を正しく入力してください"
        }
    }

    private func formatAnswer(_ answer: String) -> String {
        // 答えの小数点以下が`.0`だった場合は、`.0`を削除して答えを整数で表示する
        let formattedAnswer: String = answer.replacingOccurrences(
                of: "\\.0$",
                with: "",
                options: NSString.CompareOptions.regularExpression,
                range: nil)
        return formattedAnswer
    }
}

formatFormulaメソッドでは、入力された計算式の文字列がSwiftの計算式として評価できるように、String#replacingOccurrencesメソッドを正規表現とともに用いて、文字列の置換処理を行っています。

そのほかは、コメントを読めばどんな処理を行っているかだいたい分かるかと思います。

動作確認

それでは、最後にビルドして動作確認してみましょう。

飲み会代の合計30,000円(税抜)を、1,000円割引きクーポンを使って、7人で割り勘する場合を計算してみました。一人あたり4,486円(1円未満四捨五入)と正しく計算でき、うまく動いているようです。

以上、電卓アプリの制作お疲れさまでした!

おわりに

Swiftの特徴から、基本文法、簡単な電卓アプリの作成まで、駆け足で解説しましたが、なかなかのボリュームになってしまいました。この記事が、入門から実践に進むお役に立てば幸いです。

Swiftの入門時には、やはり Optional型とそれを取り扱う文法が一番難しいだろうと思います。逆に言うと、そこさえ攻略できれば大丈夫なわけです。あとはXcodeのGUI操作ですが、これはとにかく触って、アプリを作りながら慣れるのが早いでしょう。

XcodeのAutoLayout機能や、画面遷移の実装等にも触れたいところでしたが、文字数の都合もあり、割愛しています。SwiftやXcodeはアップデートの速度が速いので、公式ドキュメントにはぜひ触れておきましょう。

参考文献

Swiftの公式ドキュメントはこちら。

以下の日本語ドキュメントでも、機能の詳細・サンプルコード等が確認できます。

今回制作した電卓アプリのソースコードは、私のGitHubに上げていますのでご参照ください。

takafumir/CalculatorApp: CalculatorApp

それでは、今後もSwiftでのiOSアプリ開発をお楽しみください!

著者プロフィール

山野 貴史(やまの・たかふみ)@

山野 貴史
受託開発や自社プロジェクトで、Ruby on RailsやPHPによるWebアプリケーション、SwiftでのiOSアプリの制作等を行っています。主に技術系・プログラミングの記事を書いているブログ、EasyRambleを運営しています。二児の父親で子育ても奮闘中です。