AppleがWWDC2014にてSwiftを発表してから2ヶ月近くが経ちました。OS X/iOSのアプリ開発に存在するObjective-Cの壁は取り払われ、より多くの人に馴染みやすい言語として現れたSwiftはコミュニティへの新たな開発者の流入を促し既存の開発者にもより安全でモダンなスタイルでの開発を可能にした点でとても歓迎されています。
既に沢山の入門文献や言語の特徴的な振る舞いを解説した文章が日本語で世に出回っていることは承知の上でありますが、最近のbeta 3で変更になった部分やこの2ヶ月で溜まってきたナレッジをもとにあらためて言語からアプリ開発まで一貫した解説記事を残したいと思いました。
本記事の構成はまず速習Swiftで文法の基礎的なところを話し、その後Todoアプリの作成を通じてクラスや構造体、UIKitを用いたアプリ制作の具体的な話をしていきます。読者は他言語を多少触ったことがある人を想定しているので行間が空いている箇所も多いとは思いますが最後までお付き合いいただければ幸いです。
速習Swift
公式にA Swift Tourという完成されたチュートリアルがありますが、英語で書かれているのであらためて日本語でSwiftの基本的な文法を解説してみたいと思います。文章より実際のコードを多めに書いているのでPlaygroundを片手に実際に動かしながら読んでいただけると理解も早いと思います。
まずはお決まりのHello Worldから始めましょう。こんにちは世界!
println("Hello World")
変数と型
Swift には変数と定数があります。それぞれvar
とlet
で宣言しvar
で宣言されたものは変数、let
で宣言されたものは定数になります。定数は一度値を代入すると二度と変更することは出来ません。
1 2 3 4 5 6 7 8 9 |
var variable = 1 let constant = "Swift" variable = 0 // これはOK constant = "Objective-C" // これはコンパイルエラー |
これだけ見るとInt
やString
等の型がないように見えますが実際はコンパイラが自動的に解釈してくれていて変数と定数にもちゃんと型があります。例えば
1 2 3 4 |
var variable = 1 variable = "Swift" // 型が違うのでコンパイルエラー |
変数に型が違う値を代入しようとするとコンパイルエラーになります。明示的に型を宣言する時は
1 2 3 |
var variable : Int = 1 let constant : String = "Swift" |
このように名前の後に型を書きます。基本的な型を以下に列挙します。
ArrayとDictionaryには便利なリテラルが用意してあります。
1 2 3 4 5 6 |
var array = [1, 2, 3, 4, 5] var dict = [ "apple": "りんご", "orange": "みかん" ] |
辞書のリテラルに使われる括弧が{}
ではなく[]
であることに注意して下さい。あえて型を表記すると
1 2 3 4 5 6 7 8 9 |
var array : [Int] = [1, 2, 3, 4, 5] var dict : [String:String] = [ "apple": "りんご", "orange": "みかん" ] var emptyArray : [Int] = [] var emptyDict : [String:String] = [:] |
このようになります。
複数の既存の型を組み合わせてタプルという特殊な型を作ることが出来ます
1 2 3 4 5 6 7 8 |
let lang : (String, Int) = ("Swift", 0) lang.0 // Swift lang.1 // 0 |
タプルを作るときは()
の中にカンマ区切りで値を入れます。上記の例でlang
の型は(String, Int)
です。
3つ以上の型を組み合わせたり、タプルを入れ子にすることも出来ます。
値を取り出す時は.0
の様に先頭から.0,.1,.2
とアクセスします。
さらに値に名前をつけることも可能です。(2014/08/04 修正)
1 2 3 4 5 6 7 8 |
let lang : (name:String, age:Int) = (name:"Swift", age:0) lang.name // Swift lang.age // 0 |
制御構文
Swift のif文は条件式の括弧をつけてもつけなくても構いません
1 2 3 4 5 6 7 8 9 10 11 |
let length = 6 if length < 2 { println("短い") } else if length < 5 { println("普通") } else { println("長い") } // 長い |
同様にwhile文も括弧を省略することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var n = 10 while n > 0 { println(n) n-- } // 10 // 9 // 8 // 7 // 6 // 5 // 4 // 3 // 2 // 1 |
for 文にはいくつかの書き方があります。よく見かけるfor文から始めましょう
1 2 3 4 5 |
for(var i=0; i<10; i++){ println(i) } |
for文もまた括弧を省略できます
1 2 3 4 |
for var i=0; i<10; i++ { println(i) } |
for in
ループも用意されています
1 2 3 4 |
for i in 0...9 { println(i) } |
以上3つは全て同じ結果になります。
0...9
はRangeという構造体で詳しくは説明しませんがとりあえずは連続した値をもった配列という認識でいいと思います。もちろん配列もfor in
ループを使って回すことが出来ます
1 2 3 4 5 6 7 8 |
let fruits = ["りんご", "みかん", "ぶどう"] for fruit in fruits { println(fruit) } // りんご // みかん // ぶどう |
要素だけでなくインデックスも同時に取り出したい時はenumerate関数を使います
1 2 3 4 5 6 7 |
for (index, fruit) in enumerate(fruits) { println("\(index): \(fruit)") } // 1: りんご // 2: みかん // 3: ぶどう |
辞書もfor文で回すことが出来ます
1 2 3 4 5 6 7 8 9 10 11 |
let dict = [ "apple": "りんご", "orange": "みかん" ] for (key, value) in dict { println("英語: \(key), 日本語: \(value)") } // 英語: apple, 日本語: りんご // 英語: orange, 日本語: みかん |
実は配列や辞書はSequenceというプロトコルを持っていて、このプロトコルをもっているデータ構造であればなんでもfor in
ループで回すことが出来ます。Rangeやenumerate関数の返り値も同様にSequenceに適合しています。
参考: Sequence
Optional
Swiftには何もないことを示す値としてnilが存在します。
しかし通常のInt
やString
といった型に直接代入することは出来ません。
nilを扱うためにはOptional
という特殊な型で今ある型を包んでやる必要があります。
1 2 3 4 5 6 7 8 9 |
var n0 : Int? = 0 var n1 : Int = 0 n0 = nil // これはOK n1 = nil // これはコンパイルエラー |
Int?
はOptional
の糖衣構文になっていて、既存の型の最後に?
をつけることで簡単になんでもOptional型に変えることが出来ます。しかしこのままではOptional型なので普通のInt型のように扱うことが出来ません。
1 2 3 4 |
var n : Int? = 1 n + 1 // コンパイルエラー |
Optional型から元の型に戻す一番簡単な方法は!
をつけることです。
1 2 3 4 |
var n : Int? = 1 n! + 1 // 2 |
!
は文字数も少なく便利なのですがこれでは万が一n
にnil
が入っていた時にランタイムエラーになってしまってせっかくのOptional型の恩恵を受けられません。より安全に計算を行うためにはif
文をうまく使うことが出来ます。
1 2 3 4 5 6 |
var maybe : Int? = 1 if let n = maybe { n + 1 // 2 } |
もしmaybe
がnil
ならif
のブロック自体が実行されません。
実際にOptional型を使う場面として検索関数はとてもいい例になります。
1 2 3 4 5 6 7 8 9 |
func indexOf(array:String[], value: String) -> Int? { for (index, str) in enumerate(array) { if str == value { return index } } return nil } |
ここでindexOf
関数は文字列の配列から与えられた文字列を検索し、存在すればそのインデックスを、無ければnilを返すような関数です。
これを使えば
1 2 3 4 5 6 7 8 |
let fruits = ["apple", "orange", "grape"] if let index = indexOf(fruits, "peach") { println("peach は\(index)番目に存在します") } else { println("存在しません") } |
こんな風に安全な検索処理を書くことが出来ます。
indexOf 関数で\()
を使って文字列の中にindex
の値を入れています。
\()
を使うとPrintable
プロトコル適合した型の変数の値を直接文字列に埋め込むことができます。
参考: Printable
関数
まずは基本的な関数の書き方です。
1 2 3 4 5 6 7 |
func doubleValue(x: Int) -> Int { return 2 * x } doubleValue(4) // 8 |
doubleValue
はInt
型の値を受け取ってInt
型の値を返す関数です。型はInt -> Int
になります。矢印の表記は左を定義域、右を値域と考えると数学の表記と対応していて見やすいですね。返り値がない場合は関数を定義する時に->
を省略することが出来ます。
1 2 3 4 5 6 7 |
func say(message: String) { println(message) } say("Hello World") // Hello World |
say
関数の型は実際にはString -> Void
となっています。
タプルとパターンマッチを組み合わせれば複数の値を返すことも出来ます
1 2 3 4 5 6 7 8 9 |
func camphorInfo() -> (Int, String) { return (4, "Camphor-") } let (age, name) = camphorInfo() println(name) // Camphor- |
...
を使えば可変長引数を扱えます。
1 2 3 4 5 6 7 8 |
func sample(names: String...) -> String { let length = names.count return names[Int(arc4random())%length] } sample("a", "b", "c") // a, b, c のいずれか |
names はStringの配列になります。
関数は値のように扱うことも出来ます。(クロージャと呼ばれます)
1 2 3 4 |
let doubleValue = { (x: Int) -> Int in return 2 * x } |
この場合、型を明記すると以下のようになります
1 2 3 4 |
let doubleValue : Int -> Int = { (x: Int) -> Int in return 2 * x } |
クロージャの中の式が一つだけの時はreturn
を省略できます
1 2 3 4 |
let doubleValue : Int -> Int = { (x: Int) -> Int in 2 * x } |
文脈上明らかな時はクロージャ内の型表記を省略することも出来ます
1 2 |
let doubleValue : Int -> Int = { x in 2 * x } |
クロージャの引数には第一引数から初めて$0, $1, ...
と名前が付いているので
1 2 |
let doubleValue : Int -> Int = { 2 * $0 } |
とも書けます。ずいぶんシンプルになりましたね
関数の引数として関数を渡したり、関数の返り値として関数を返すことも出来ます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
let doubleValue : Int -> Int = { 2 * $0 } func calc(arg: Int, f: Int -> Int) -> Int { return f(arg) } calc(4, doubleValue) // 8 calc(4) { 3 * $0 } // 12 // 最後の引数にクロージャを渡す場合はこのように括弧を省略することができます。 func add(x: Int) -> Int -> Int { func addX(y: Int) -> Int { return x + y } return addX; } let add10 = add(10) add10(5) // 15 |
最後の例はカリー化を使って次のように書くことも出来ます
1 2 3 4 5 6 7 8 9 |
func add(x: Int)(y: Int) -> Int { return x + y } let add7 = add(7) add7(8) // 15 |
ここまで駆け足で基本的な文法を説明してきました。より詳しい内容が知りたい人にはA Swift Tourがオススメです。英語にはなりますがとても丁寧ですし公式なので一番正確な内容が書かれています。
これ以降は実践で学んで行きましょう。
Todoアプリを作る
ここからは実際にアプリを作りながら解説していきます。Appleの開発者登録している人はXcode6 beta を立ち上げてください。プロジェクトを新規作成し、iOS の Empty Application を作ってください。言語等はSwift用に設定しましょう。ProductionName は TodoApp としておきます
こんな感じのフォルダ構成になっていれば大丈夫です
1 2 3 4 5 |
TodoApp ├── AppDelegate.swift ├── Images.xcassets └── Supporting Files/ |
オブジェクトとクラス
いちばん単純なクラスは
1 2 3 |
class SomeClass { } |
このように書きます。ちなみにこれでコンパイルも通ります。
これだけでは少し寂しいのでプロパティもつけましょう
1 2 3 4 |
class SomeClass { var someInt = 1 } |
さらにメソッドもつけましょう
1 2 3 4 5 6 7 |
class SomeClass { var someInt = 1 func showSomeInt() { println(someInt) } } |
作ったクラスから実際にインスタンスを生成する時は
1 2 3 4 5 |
let some = SomeClass() some.showSomeInt() // 1 |
このように関数呼び出しのようにクラス名の後ろに()
をつけます。new
やalloc] init]
を書く必要はありません。
クラスを継承をしたい時は
1 2 3 |
class SomeSubClass : SomeClass { } |
このようにクラス名に:
を続けてさらに継承したいクラス名を書きます。新たに加えるプロパティやメソッドは通常のクラスと同じように書いていきますが、親クラスのメソッドをオーバーライドする時は
1 2 3 4 5 6 |
class SomeSubClass : SomeClass { override func showSomeInt() { println("Some Int is \(someInt)") } } |
このように関数名の前にoverride
と書きます。継承クラスでのオーバーライドはコードを読みにくくすることも多いのでひと目で分かるような仕組みがあるのはとてもありがたいですね。
それではTodoアプリの方に戻りましょう。TodoTableViewController.swift
というファイルを作ってください。最初の画面を作るためにUIViewController
を継承したTodoTableViewController
というクラスを作ります。
1 2 3 4 5 6 7 8 9 |
import UIKit class TodoTableViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() } } |
上のコードではviewDidLoad
メソッドをオーバーライドして更にsuper.viewDidLoad()
で親クラスのviewDidLoad
メソッドを呼び出しています。
TodoTableViewController
を実際に表示してみましょう。AppDelegate.swift
を開いて以下のように書き換えてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { self.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.window!.rootViewController = TodoTableViewController() self.window!.backgroundColor = UIColor.whiteColor() self.window!.makeKeyAndVisible() return true } } |
おそらく追加したのは
1 2 |
self.window!.rootViewController = TodoTableViewController() |
の1行だと思います。TodoTableViewController
のインスタンスをself.window!
のrootViewController
プロパティに代入していますね。もちろんself.window
はOptional型として宣言されているので使う時は!
をつけてやる必要があります。この行でアプリが起動して最初に表示される画面をTodoTableViewController
に設定しています。しかし現時点で起動することはできますが何もない真っ白な画面が表示されるだけです。この真っ白な画面にUI部品を配置していくためにTodoTableViewController.swift
に戻りましょう。最初は文字を表示してみます。
1 2 3 4 5 6 7 8 |
override func viewDidLoad() { super.viewDidLoad() let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" self.view.addSubview(title) } |
追加した三行では
- UILabel のインスタンスを生成して
title
に束縛する title
のtext
プロパティに”Todoリスト”という文字を入れるTodoTableViewController
のview
プロパティにtitle
をaddSubview
する
ということをしています。とりあえず実行してみると画面に文字が表示されるでしょう。UI部品を表示する時はUIView
のオブジェクトを作ってaddSubview
するのが基本になるので覚えておいてください。
次は画像を表示してみましょう。以下の画像をheader@2x.png
という名前で保存してください。
そしたらFinderからXcode上のImages.xcassets
にドラッグ&ドロップ。これで作業は完了です。
TodoTableViewController.swift
に戻ってviewDidLoad
に以下のコードを加えます
1 2 3 4 5 6 7 8 9 10 11 12 |
override func viewDidLoad() { super.viewDidLoad() let header = UIImageView(frame: CGRect(x: 0, y: 0, width: 320, height: 64)) header.image = UIImage(named:"header") self.view.addSubview(header) let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" self.view.addSubview(title) } |
追加した三行は
- まず画面の左上に320×64の大きさで
UIImageView
を作る - “header”という名前の画像を追加する(実際には”header@2x.png”が使われます)
- 画面に表示する(表示されているViewに追加する)
ということをしています。
タイトルとヘッダーをまとめてみましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 |
override func viewDidLoad() { super.viewDidLoad() let header = UIImageView(frame: CGRect(x: 0, y: 0, width: 320, height: 64)) header.image = UIImage(named:"header") let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" header.addSubview(title) self.view.addSubview(header) } |
タイトルを直接self.view
に追加するのではなく、まずheader
に追加してからheader
をself.view
に追加することで表示しています。こうすることでヘッダーを移動させるときもheader
だけ動かせばtitle
も自動的についてくるようになります。
それではTODOを表示するためのテーブルを追加していきましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var tableView : UITableView? override func viewDidLoad() { super.viewDidLoad() let header = UIImageView(frame: CGRect(x: 0, y: 0, width: 320, height: 64)) header.image = UIImage(named:"header") let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" header.addSubview(title) let screenWidth = UIScreen.mainScreen().bounds.size.height self.tableView = UITableView(frame: CGRect(x: 0, y: 60, width: 320, height: screenWidth - 60)) self.view.addSubview(header) } |
なかなかコードが増えてきましたね。追加したのは
1 2 |
var tableView : UITableView? |
と
1 2 3 |
let screenHeight = UIScreen.mainScreen().bounds.size.height self.tableView = UITableView(frame: CGRect(x: 0, y: 60, width: 320, height: screenHeight - 60)) |
の2箇所です。まずtableView
をプロパティとして宣言しています。この時点では初期値を代入できないので型をOptional型にしています。こうすることで初期値としてnilが入るようになります。次に画面の高さを取得してUITableView
のインスタンスを作成しています。
UITableView
の動作を理解するためにプロトコルの概念が必要不可欠なのでここで説明しておきましょう。プロトコルはそのクラス(もしくは構造体)がどのように振る舞うのか、どのようなプロパティ・メソッドを持っているのかを宣言するためのものです。具体的には以下のように書きます
1 2 3 4 5 6 |
protocol SomeProtocol { func someMethod() } |
使い方は
1 2 3 4 5 6 7 |
class SomeClass : SomeProtocol { // このメソッドを実装していないとコンパイラに怒られる func someMethod() { println("Hello") } } |
こんな感じです。SomeClass
はSomeProtocol
に適合することでsomeMethod
を持っていることが保証されます。プロトコルは継承と違って一つのクラスにいくつも持たせることができるので振る舞いを宣言するのにとても便利な道具です。ちなみにプロトコルに適合させる文法は継承と同じになっていますが、継承とプロトコルを同時に使いたい時は
1 2 3 4 5 6 7 8 9 10 |
class Parent { } class Children : Parent, SomeProtocol { // このメソッドを実装していないとコンパイラに怒られる func someMethod() { println("Sub Class") } } |
このように親クラスを先に書いてカンマ区切りでプロトコルを続けて書いていきます。
TodoTableViewController.swift
に戻りましょう。
1 2 3 4 5 6 7 8 |
import UIKit class TodoTableViewController : UIViewController, UITableViewDataSource { // 実装は省略 } |
TodoTableViewController
にUITableViewDataSource
のプロトコルを追加します。このプロトコルが要請するのは
1 2 3 |
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! |
この2つのメソッドです(それ以外は@optional)。まずは適当に実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import UIKit class TodoTableViewController : UIViewController, UITableViewDataSource { var tableView : UITableView? override func viewDidLoad() { // 実装省略 } func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return 10 } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let cell = UITableViewCell(style: .Default, reuseIdentifier: nil) cell.textLabel.text = "todo" return cell } } |
tableView(_:numberOfRowsInSection:)
では表示するテーブルの行数を返しています。
tableView(_:cellForRowAtIndexPath:)
では表示するセルを生成して返しています。
それではviewDidLoad
を仕上げましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var tableView : UITableView? override func viewDidLoad() { super.viewDidLoad() // 省略 let screenWidth = UIScreen.mainScreen().bounds.size.height self.tableView = UITableView(frame: CGRect(x: 0, y: 60, width: 320, height: screenWidth - 60)) self.tableView!.dataSource = self self.view.addSubview(self.tableView!) self.view.addSubview(header) } |
追加したのはデータソースを設定した行とaddSubview
の行です。実行してみてください。ちゃんとtodo
と書かれたテーブルが表示されましたでしょうか?とりあえず現時点でのコードを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import UIKit class TodoTableViewController : UIViewController, UITableViewDataSource { var tableView : UITableView? override func viewDidLoad() { super.viewDidLoad() let header = UIImageView(frame: CGRect(x: 0, y: 0, width: 320, height: 64)) header.image = UIImage(named:"header") let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" header.addSubview(title) let screenWidth = UIScreen.mainScreen().bounds.size.height self.tableView = UITableView(frame: CGRect(x: 0, y: 60, width: 320, height: screenWidth - 60)) self.tableView!.dataSource = self self.view.addSubview(self.tableView!) self.view.addSubview(header) } func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return 10 } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let cell = UITableViewCell(style: .Default, reuseIdentifier: nil) cell.textLabel.text = "todo" return cell } } |
次にTODOデータを表現するデータ構造を作っていきます。TodoDataManager.swift
というファイルを新しく作ってください。
まずTODOのデータ構造を定義します
1 2 3 4 |
struct TODO { var title : String } |
これは構造体と呼ばれるもので、クラスとほとんど同等の機能を持ちますが大きな違いは値渡しと参照渡しの違いです。構造体は値渡しなので変数に代入されるときにまるまるコピーされるのに対してクラスはその参照だけがコピーされます。大きく複雑なデータを扱うのには向きませんが、今回のTODOのようなデータは構造体にしたほうが扱いやすいでしょう。
参考: Class and Structures
これを使ってTODOリストを表現するデータ構造を作ります
1 2 3 4 5 6 7 |
class TodoDataManager { var todoList: [TODO] init(){ self.todoList = [] } } |
init
は特殊なメソッドで初期化の際に使われます。
1 2 3 4 5 |
let data = TodoDataManager() // init() が実行される data.todoList // Empty Array |
init
は重要な概念ですが解説すると長くなりそうなので今回は割愛させていただきます。
参考: Initialization
TodoDataManager
に基本的な機能を追加していきましょう。まずは生成・更新・削除が出来るようにしてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
class TodoDataManager { var todoList: [TODO] init(){ self.todoList = [] } class func validate(todo: TODO!) -> Bool { return todo != nil && todo != "" } func create(todo: TODO!) -> Bool { if TodoDataManager.validate(todo) { self.todoList += todo return true } return false } func update(todo: TODO!, at index: Int) -> Bool { if(index >= self.todoList.count) { return false } if TodoDataManager.validate(todo) { todoList[index] = todo self.save() return true } return false } func remove(index: Int) -> Bool { if(index >= self.todoList.count) { return false } self.todoList.removeAtIndex(index) self.save() return true } } |
追加したのは以下の四つのメソッドです。
1 2 3 4 5 |
class func validate(todo: TODO!) -> Bool func create(todo: TODO!) -> Bool func update(todo: TODO!, at index: Int) -> Bool func remove(index: Int) -> Bool |
validate
メソッドはtodoが正しい書式かどうかを判定するメソッドで、値がnil
でないかと空文字でないかを判定しています。これはクラスメソッドとして定義されていて、見て分かるようにクラス名から直接呼び出すのでインスタンスに依存すること無く使える関数です。create
とupdate
とremove
がそれぞれself.todoList
の内容を生成・更新・削除するメソッドになっています。
肝心のTodoDataManager
からデータを読み出す機能を実装しましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class TodoDataManager { var size : Int { return todoList.count } subscript(index: Int) -> TODO { return todoList[index] } // 実装省略 } |
size
プロパティは現在格納されているTODOの総数を返します。少し変わった書き方をしていますが、これはComputed Propertyと呼ばれるものでそのプロパティに値を代入する時と値を取り出すときに実行される処理を書くことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var size : Int { get { return 10 } set { println(newValue) } } size = 12 // 12 size // 10 |
上の例で set にある newValue は新たに代入される値を参照しています。
set 節を省略して書くことも出来ます。
1 2 3 4 5 6 7 8 9 |
var size : Int { get { return 10 } } size // 10 |
setを省略した場合はプロパティに代入することができなくなり読み取り専用になります。
get のみの場合は get { }
を省略することも出来ます
1 2 3 4 5 6 7 |
var size : Int { return 10 } size // 10 |
これで最初の形になりました。
subscript(index: Int) -> TODO
はTodoDataManager
に[]
を使ってアクセスされた時の振る舞いを記述します。
1 2 3 4 5 6 7 8 |
var data = TodoDataManager() data.create("くう") data.create("ねる") data.create("あそぶ") data[1] // ねる |
まるで配列のように中身にアクセスが出来るようになりました。このsubscript
を実装していればどんなクラスや構造体であっても[]
による振る舞いを追加することが出来ます。また[]
の中にとる値の型もInt
だけでなく任意の型を使うことが出来ます。
次にアプリ内にデータを保存してアプリを終了してもTODOが消えないようにしてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class TodoDataManager { let STORE_KEY = "TodoDataManager.store_key" init() { let defaults = NSUserDefaults.standardUserDefaults() if let data = defaults.objectForKey(self.STORE_KEY) as? [String] { self.todoList = data.map { title in TODO(title: title) } } else { self.todoList = [] } } func save() { let defaults = NSUserDefaults.standardUserDefaults() let data = self.todoList.map { todo in todo.title } defaults.setObject(data, forKey: self.STORE_KEY) } // 実装省略 } |
初期化の際にNSUserDefaults
からデータを読み出す処理と保存するsave
メソッドを追加しました。NSUserDefaults
はplist
という形式を使ってアプリ内にデータを保存します。アプリを閉じてもデータは記録されたままでstandardUserDefaults
メソッドが返すオブジェクトからいつでも取り出すことができます。ただし独自に作った構造体をそのまま保存することはできないのでここではTODOのStringのみ保存するようにしています。
create
, update
, delete
からこのsave
メソッドを呼ぶようにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class TodoDataManager { // 実装省略 func create(todo: TODO!) -> Bool { if TodoDataManager.validate(todo) { self.todoList += todo self.save() return true } return false } func update(todo: TODO!, at index: Int) -> Bool { if(index >= self.todoList.count) { return false } if TodoDataManager.validate(todo) { self.todoList[index] = todo self.save() return true } return false } func remove(index: Int) -> Bool { if(index >= self.todoList.count) { return false } self.todoList.removeAtIndex(index) self.save() return true } } |
これでデータに修正が加わるたびにちゃんと保存されるようになりました。
TodoDataManager
はTODOのデータを中央管理しているのでひとつのオブジェクトを色んな所で使いまわす必要が出てくると思います。そこで最後にこのクラスをシングルトンとして使えるようにしましょう
1 2 3 4 5 6 7 8 9 10 11 12 |
class TodoDataManager { class var sharedInstance : TodoDataManager { struct Static { static let instance : TodoDataManager = TodoDataManager() } return Static.instance } // 実装省略 } |
現状ではクラス変数を作ることができないので構造体変数を利用してシングルトンを実現しています。static
をlet
の前につけることでそのプロパティが型に紐付いたものであることを表しています。こうしておけば
1 2 3 4 5 6 7 |
let data0 = TodoDataManager.sharedInstance let data1 = TodoDataManager.sharedInstance data0 === data1 // true // === は変数の参照が同じかどうかを判定する演算子 |
となりTodoDataManager.sharedInstance
で常に同じインスタンスを取り出すことが可能です。
参考: hpique/SwiftSingleton
以上でTodoDataManager
は完成です。完成したものがこちらです。
それではTodoDataManager
で管理しているTODOをTodoTableViewController
のテーブルに表示するようにしてみましょう
TodoTableViewController.swift
を開いてください。
作っていく前に一つ新しい概念を使ってよりSwiftらしい書き方に変えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class TodoTableViewController : UIViewController { // 実装省略 // tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int と // tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! を // 下に移動する } extension TodoTableViewController : UITableViewDataSource { func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return self.todo.size } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { // 実装省略 } } |
プロトコルの部分だけextension
で分離しています。extension
は既存の型に新たな機能をあとから付け加えることの出来る機能です。例えば
1 2 3 4 5 6 7 8 9 |
extension Int { func show() { println(self) } } 8.show() // 8 |
Int型でさえも後から拡張することが出来ます。これを利用してプロトコルが要請するメソッドの記述だけ分離しているわけですね。こうすることで実装を整理できプロトコルが後から付け加えられても見やすさを保つことが出来ます。
それではあらためてTodoDataManager
の内容を表示できるようにしていきましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class TodoTableViewController : UIViewController { var todo = TodoDataManager.sharedInstance // 実装省略 } extension TodoTableViewController : UITableViewDataSource { func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return self.todo.size } func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let row = indexPath.row let cell = UITableViewCell(style: .Default, reuseIdentifier: nil) cell.textLabel.text = self.todo[row].title return cell } } |
これでTodoDataManager
で管理しているデータが表示されるはずです!しかし今はまだ何もデータが入っていないので何も表示されません。TODOを追加するUIを実装していきましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
override func viewDidLoad() { super.viewDidLoad() let header = UIImageView(frame: CGRect(x: 0, y: 0, width: 320, height: 64)) header.image = UIImage(named:"header") header.userInteractionEnabled = true let title = UILabel(frame: CGRect(x: 10, y: 20, width: 310, height: 44)) title.text = "Todoリスト" header.addSubview(title) let button = UIButton.buttonWithType(.System) as UIButton button.frame = CGRect(x: 320 - 50, y: 20, width: 50, height: 44) button.setTitle("追加", forState: .Normal) button.addTarget(self, action:"showCreateView", forControlEvents: .TouchUpInside) header.addSubview(button) let screenWidth = UIScreen.mainScreen().bounds.size.height self.tableView = UITableView(frame: CGRect(x: 0, y: 60, width: 320, height: screenWidth - 60)) self.tableView!.dataSource = self self.view.addSubview(self.tableView!) self.view.addSubview(header) } |
増えた箇所は
1 2 |
header.userInteractionEnabled = true |
と
1 2 3 4 5 6 |
let button = UIButton.buttonWithType(.System) as UIButton button.frame = CGRect(x: 320 - 50, y: 20, width: 50, height: 44) button.setTitle("追加", forState: .Normal) button.addTarget(self, action:"showCreateView", forControlEvents: .TouchUpInside) header.addSubview(button) |
の2箇所です。userInteractionEnabled
を true
にすることでheader
にaddSubview
するボタンが正常に動くようにしています。button
にはaddTarget
でタップされた時に呼び出す関数をしていしています。しかしまだこのshowCreateView
関数は存在していないので作っていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
class TodoTableViewController : UIViewController { // 実装省略 func showCreateView() { let alert = UIAlertController(title: "Todoを追加する", message: nil, preferredStyle: .Alert) self.presentViewController(alert, animated: true, completion: nil) } } |
これで追加ボタンを押すと Todoを追加 するという表示が出るようになったと思います。
参考: UIAlertController
このままだと入力ができないのでテキストフィールドを追加しましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class TodoTableViewController : UIViewController { var alert : UIAlertController? // 実装省略 func showCreateView() { self.alert = UIAlertController(title: "Todoを追加する", message: nil, preferredStyle: .Alert) self.alert!.addTextFieldWithConfigurationHandler({ textField in textField.delegate = self }) self.presentViewController(self.alert, animated: true, completion: nil) } } extension TodoTableViewController : UITextFieldDelegate { func textFieldShouldEndEditing(textField: UITextField!) -> Bool { let todo = TODO(title: textField.text) if self.todo.create(todo) { textField.text = nil self.tableView!.reloadData() } self.alert!.dismissViewControllerAnimated(false, completion: nil) return true } } |
self.alert
に UITextField
を追加して入力が終わったらtextFieldShouldEndEditing
が呼び出されるようにしました。ここらへんはプロトコルをうまく利用しています。textFieldShouldEndEditing
では入力値をもとに新たにTODO
を作成して登録したあとself.alert
を閉じています。
これで新たなTODOを追加していけるようになりました。次に編集と削除が出来るようにしてみましょう。編集と削除のボタンはそれぞれのTODOのセルにあったほうが自然なのでそのセル自体を作っていきます。TodoTableViewCell.swift
というファイルを作りましょう。
1 2 3 4 5 6 |
import UIKit class TodoTableViewCell : UITableViewCell { } |
UITableViewCell
を継承したTodoTableViewCell
というクラスを作ります。iPhoneに標準で入ってるリマインダーのようにスワイプしたら編集・削除ボタンが出てくるような仕組みを作ってみましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class TodoTableViewCell : UITableViewCell { var haveButtonsDisplayed = false init(style: UITableViewCellStyle, reuseIdentifier: String!) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .None self.contentView.backgroundColor = UIColor.whiteColor() self.contentView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: "hideDeleteButton")) let swipeRecognizer = UISwipeGestureRecognizer(target: self, action: "showDeleteButton") swipeRecognizer.direction = .Left self.contentView.addGestureRecognizer(swipeRecognizer) } func showDeleteButton() { if !haveButtonsDisplayed { UIView.animateWithDuration(0.1, animations: { let size = self.contentView.frame.size let origin = self.contentView.frame.origin self.contentView.frame = CGRect(x: origin.x - 100, y:origin.y, width:size.width, height:size.height) }) { completed in self.haveButtonsDisplayed = true } } } func hideDeleteButton() { if haveButtonsDisplayed { UIView.animateWithDuration(0.1, animations: { let size = self.contentView.frame.size let origin = self.contentView.frame.origin self.contentView.frame = CGRect(x: origin.x + 100, y:origin.y, width:size.width, height:size.height) }) { completed in self.haveButtonsDisplayed = false } } } } |
いきなりたくさん書きましたが一つずつ説明していきます。まずinit(style: UITableViewCellStyle, reuseIdentifier: String!)
では親クラスのsuper.init(style: style, reuseIdentifier: reuseIdentifier)
を呼び出しています。self.selectionStyle = .None
でタップされた時の挙動を何もしないように設定しています。.None
は正確に書くとUITableViewCellSelectionStyle.None
でUITableViewCellSelectionStyle
という列挙子の一つを表しています。この場合は左辺がUITableViewCellSelectionStyle
型のプロパティであり文脈上明らかなので省略して.None
とだけ書くことが出来ます。
1 2 |
self.contentView.backgroundColor = UIColor.whiteColor() |
まずcontentView
の背景色を白にしています。
1 2 |
self.contentView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: "hideDeleteButton")) |
ここではGestureRecognizer
の生成と登録を同時に行っています。UISwipeGestureRecognizer
は右方向へのスワイプを認識するためのものでこの場合contentView
上で右方向へのスワイプが行われるとhideDeleteButton
メソッドが実行されます。
1 2 3 4 |
let swipeRecognizer = UISwipeGestureRecognizer(target: self, action: "showDeleteButton") swipeRecognizer.direction = .Left self.contentView.addGestureRecognizer(swipeRecognizer) |
これは左方向へのスワイプが行われるとshowDeleteButton
メソッドが実行されるようにしています。
showDeleteButton
の中を見て行きましょう。
1 2 3 4 5 6 7 8 9 10 |
if !self.haveButtonsDisplayed { UIView.animateWithDuration(0.1, animations: { let size = self.contentView.frame.size let origin = self.contentView.frame.origin self.contentView.frame = CGRect(x: origin.x - 100, y:origin.y, width:size.width, height:size.height) }) { completed in self.haveButtonsDisplayed = true } } |
まずself.haveButtonsDisplayed
の真偽値で開いた状態か閉じた状態かを判断しています。そのあとUIView.animateWithDuration
でアニメーション後の状態を指定して0.1秒で動くようにしています。こうすることで今の状態からアニメーション後の状態に向かって0.1秒で変化するように自動的に計算してくれます。アニメーションが完了したらself.haveButtonsDisplayed = true
でボタンが表示された状態であるように値を更新しています。hideDeleteButton
の中ではこれと反対のことをしています。
それでは編集・削除ボタンを表示してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
init(style: UITableViewCellStyle, reuseIdentifier: String!) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.selectionStyle = .None self.createView() let swipeRecognizer = UISwipeGestureRecognizer(target: self, action: "showDeleteButton") swipeRecognizer.direction = .Left self.contentView.addGestureRecognizer(swipeRecognizer) self.contentView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: "hideDeleteButton")) } func createView() { let origin = self.frame.origin let size = self.frame.size self.contentView.backgroundColor = UIColor.whiteColor() let updateButton = UIButton.buttonWithType(.System) as UIButton updateButton.frame = CGRect(x: size.width - 100, y: origin.y, width: 50, height: size.height) updateButton.backgroundColor = UIColor.lightGrayColor() updateButton.setTitle("編集", forState: .Normal) updateButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) updateButton.addTarget(self, action: "updateTodo", forControlEvents: .TouchUpInside) let removeButton = UIButton.buttonWithType(.System) as UIButton removeButton.frame = CGRect(x: size.width - 50, y: origin.y, width: 50, height: size.height) removeButton.backgroundColor = UIColor.redColor() removeButton.setTitle("削除", forState: .Normal) removeButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) removeButton.addTarget(self, action: "removeTodo", forControlEvents: .TouchUpInside) self.backgroundView = UIView(frame: self.bounds) self.backgroundView.addSubview(updateButton) self.backgroundView.addSubview(removeButton) } |
init(style: UITableViewCellStyle, reuseIdentifier: String!)
の中で描画に関する部分をcreateView
に切り出しています。
このままだとボタンが押されても何も動作しないのでTODOを編集・削除する機能をupdateTodo
, removeTodo
に実装していきたいのですがTodoTableViewCell
はTodoDataManager
のインスタンスを持っておらずどうすればいいかわかりません。TodoDataManager
はシングルトンなのでそれを取得すればいいのですがその後にテーブルを更新しなければならずそれはTodoTableViewCell
の中ではなくTodoTableViewController
の中で行う必要があります。このように動作を他のオブジェクトに”委譲”するような仕組みをデリゲートパターンといいます。今回はプロトコルを使ってデリゲートを実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@objc protocol TodoTableViewCellDelegate { @optional func updateTodo(index: Int) @optional func removeTodo(index: Int) } class TodoTableViewCell : UITableViewCell { weak var delegate: TodoTableViewCellDelegate? // 実装省略 func updateTodo() { delegate?.updateTodo?(self.tag) } func removeTodo() { delegate?.removeTodo?(self.tag) } } |
@optional
アノテーションをメソッドの前につけるとそのメソッド・プロパティは実装していても実装していなくても良くなります。ただし@optional
を使うには@objc
アノテーションをprotocol
の前につける必要があります。@objc
アノテーションを付けたプロトコルはNSObject
かNSProxy
を継承したクラスにしか適用できなくなります。今の場合はUIViewController
を継承したTodoTableViewController
に適用しようとしてるので問題無いですね!
デリゲートとして使う変数は以下のように宣言しています。
1 2 |
weak var delegate: TodoTableViewCellDelegate? |
weak
はメモリ管理に関連するものでdelegate
に入れられたオブジェクトのリファレンスカウントを増やさないようにするものです。
この場合はTodoTableViewCell
がTodoTableViewController
を保持するようにするので循環参照を起こさないようにしています。
updateTodo
, removeTodo
が呼び出されるとそのままデリゲートオブジェクトのupdateTodo
, removeTodo
が呼び出されるようになっていますね。
以上でTodoTableViewCell
は完成です。出来上がったコードはこちらです。
作ったTodoTableViewCell
をTodoTableViewController
の方に反映させましょう(追記 2014/07/23)
1 2 3 4 5 6 7 8 9 10 |
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { let cell = TodoTableViewCell(style: .Default, reuseIdentifier: nil) cell.delegate = self cell.textLabel.text = self.todo[indexPath.row].title cell.tag = indexPath.row return cell } |
最後にTodoTableViewController
にTodoTableViewCellDelegate
用のメソッドを実装して編集・削除機能を実装しましょう。
1 2 3 4 5 6 7 |
extension TodoTableViewController : TodoTableViewCellDelegate { func updateTodo(index: Int) { } func removeTodo(index: Int) { } } |
ここに実装していくのですが、生成・編集・削除をうまく処理するためにそれぞれの状態であることを表現出来るようにします
1 2 3 4 5 6 7 8 |
enum TodoAlertViewType { case Create, Update(Int), Remove(Int) } class TodoTableViewController : UIViewController { // 実装省略 } |
このようにenum
を追加してください。
これは
- TodoAlertViewType.Create
- TodoAlertViewType.Update
- TodoAlertViewType.Remove
という3つの列挙子を作り、さらにUpdate
とRemove
にはそれぞれ編集・削除する対象のインデックスを連想値として持たせています。これを使って
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class TodoTableViewController : UIViewController { var alertType : TodoAlertViewType? func showCreateView() { self.alertType = TodoAlertViewType.Create // 実装省略 } } extension TodoTableViewController : TodoTableViewCellDelegate { func updateTodo(index: Int) { self.alertType = TodoAlertViewType.Update(index) self.alert = UIAlertController(title: "編集", message: nil, preferredStyle: .Alert) self.alert!.addTextFieldWithConfigurationHandler({ textField in textField.text = self.todo[index].title textField.delegate = self }) self.presentViewController(self.alert, animated: true, completion: nil) } func removeTodo(index: Int) { self.alertType = TodoAlertViewType.Remove(index) self.alert = UIAlertController(title: "削除", message: nil, preferredStyle: .Alert) self.alert!.addAction(UIAlertAction(title: "Delete", style: .Destructive) { action in self.todo.remove(index) self.tableView!.reloadData() }) self.alert!.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)) self.presentViewController(self.alert, animated: true, completion: nil) } } |
このように実装していきます。方針は
- alertType に生成・編集・削除のどれであるかを設定して
- UIAlertController のインスタンスを生成して表示する
です。これに伴ってUITextFieldDelegate
の実装を以下のように変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
extension TodoTableViewController : UITextFieldDelegate { func textFieldShouldEndEditing(textField: UITextField!) -> Bool { if let type = self.alertType { switch type { case .Create: let todo = TODO(title: textField.text) if self.todo.create(todo) { textField.text = nil self.tableView!.reloadData() } case let .Update(index): let todo = TODO(title: textField.text) if self.todo.update(todo, at:index) { textField.text = nil self.tableView!.reloadData() } case let .Remove(index): break } } self.alert!.dismissViewControllerAnimated(false, completion: nil) return true } } |
これで全て完成しました。TodoTableViewController.swift
のコードはこちらです。
完成したTodoApp
はGithubにあげています。読んだだけの人もぜひ実際に動かしてみてください。
あとがき
以上Swiftでのアプリ制作についてかなり飛ばし気味ではありましたが説明してきました。Swiftが発表されてから2ヶ月近く経とうとしていますが一番の情報源はやはり公式ドキュメントだと思います。しかし公式ドキュメントは英語なので英語が苦手だという方は、日本語だとQiitaが雑多な情報が集まっていてオススメです。
再び英語にはなりますがGithubにも既にたくさんのSwiftのプロジェクトがあるので実際にコードを読むととても勉強になると思います。自分も__.swiftというライブラリを公開しているので勉強ついでに覗きに来てもらえると幸いです。
Swiftで書いたアプリはまだApp Storeに申請することは出来ません。正式リリースは今年の秋をまたなければいけないでしょう。しかし今からSwiftを学んでおいて損は全くないと思います。長い解説記事になってしまいましたが最後まで読んでいただいてありがとうございました。これを機に一人でも多くSwift開発者が増えると幸いです。
ひろせ
名前付きのタプルについて間違いがあるようです。
ご確認ください。
ありがとうございます。修正しました
本日午後から楽しく拝見させて頂きました(個人的にはとてもエキサイティングな時間でした)。
手順に従いTodoAppを最後まで実装してみました。
私の読み落としかも知れませんが、カスタムセルの下記書き替えの記載が漏れているような気がしました。
【初期実装】
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let row = indexPath.row
let cell = UITableViewCell(style: .Default, reuseIdentifier: nil)
cell.textLabel.text = self.todo[row].title
return cell
}
↓
【カスタムセル対応の書き替え】
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = TodoTableViewCell(style: .Default, reuseIdentifier: nil)
cell.delegate = self
cell.textLabel.text = self.todo[indexPath.row].title
cell.tag = indexPath.row
return cell
}
大変参考になります、ありがとうございました。
ありがとうございます。修正しました
わかりやすい情報ありがとうございます。
swiftの勉強に参考にさせていただいております。
さて、Githubよりソースを入手させていただき、ビルドしているのですが、
TodoDataManagerの var todoList: [TODO]の部分にて下記エラーがでており、
ビルドできない状況になっております。
Consecutive declarations on a line must be separated by ‘;’
何か環境設定など必要なのでしょうか。差し支えなければ教えていただきたく。
ありがとうございます。あらためて手元で確認しましたが正常に動作しました。おそらくお使いになっているXcodeのバージョンが古いものではないでしょうか?現時点で最新の Xcode6 beta4 で試してみてください。
ご返答ありがとうございます。
アドバイスいただいた通り、手前の環境はXcode beta2でした。
最新バージョンで確認してみたいと思います。ありがとうございました。
初心者です。勉強になります。下記の所、エラーとなりますが、
☆部分を挿入すればいいという事でしょうか。
func add(x: Int) -> Int -> Int {
func addX(y: Int) -> Int {
return x + y
}
return addX //☆
}
let add10 = add(10)
add10(5)
// 15
ありがとうございます。ご指摘の通りでしたので修正しました。
非常にわかりやすいです、ありがとうございます!
私の環境はxcode6 beta6なのですが、
class TodoDataManagerの
class func validate(todo: TODO!) -> Bool {
return todo != nil && todo != “”
}
でエラーがでてしまい
return todo != nil && todo.title != “”
これで通りました、それと
func create(todo: TODO!) -> Bool {
if TodoDataManager.validate(todo) {
self.todoList += todo
self.save()
return true
}
return false
}
は+=が使えなかったので(beta5からarrayの+=の仕様が変わっていましたがstructも??)
self.todoList.append(todo)
でいけました、あともう一点
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) ->
cellForRowAtIndexPathを削除したらビルド通りました、ちょっと理由はまだわかりません
beta4用の記事でしたら申し訳ございません
ものすごく参考になりました。
Java プログラマな身にも分かりやすかったです。
Github のほうで Xcode6 beta7 のプルリクが来ているようなので、取り込んでいただけますとありがたいです★
(beta7 環境だと、コンパイルエラーに悩まされましたので)
XcodeのバージョンアップでEmpty Applicationが選択できなくなったらしいので、そのあたりの対応もできればお願いします。
Optional型説明の最後の部分に間違いがあるようです。
ご確認ください。
indexOf 関数で\()を使って文字列の中にindexの値を入れています。
↑indexOfではなくprintlnではないでしょうか?
XCode6にはEmpty Applicationがないので、この記事が役にたたない。
StoryBoard使ったやり方にかえてほしい。
もちろんEmptyApplication作る方法あるけど、Appleが進めないやり方やっても
あまり意味がないと思う。
お元気ですか
まじめに読んでいたら突然の「くうねるあそぶ」でめちゃうけましたw
せっかくの良い記事なので正式版の内容にきちんと修正されてはいかがでしょうか、(配列の追加で、+=演算子が廃止になった点など)
GitHubの修正されているようですが
ピンバック: CAMPHOR- Advent Calendar 2014をまとめるよ! - CAMPHOR- Blog
ピンバック: Apple新言語「Swift」ことはじめ。勉強用リンクまとめ。 | イリテク
ピンバック: JavaScriptプログラマがSwift iOSアプリを2週間で作って公開してみた〜その2 Swift Programming for JavaScript Programmer〜 | てじーまーのblog
ピンバック: 【文系エンジニア20歩目】8月2日『iOS開発難しいよ!!』 | devtheage
ピンバック: Swift/iOSアプリ作成 | Otacky blog
ピンバック: Swift/iOSアプリ作成 – Otacky blog
ピンバック: Swift/iOSアプリ作成
ピンバック: Swiftを勉強してみた1 〜型〜 - YUEDY
ピンバック: スパルタキャンプ1日目│プログラミングなう