Guide for Swift and Viewer app

74
#kaneshin © 2014 Shintaro Kaneko Guide for Swift and Viewer App Shintaro Kaneko iOS/Android/Web Developer

Transcript of Guide for Swift and Viewer app

Page 1: Guide for Swift and Viewer app

#kaneshin

© 2014 Shintaro Kaneko

Guide for Swift

and Viewer App

Shintaro Kaneko iOS/Android/Web Developer

Page 2: Guide for Swift and Viewer app

Agenda

Guide for Swift Viewer App

Page 3: Guide for Swift and Viewer app

Guide for Swift

Page 4: Guide for Swift and Viewer app

Guide for Swift

Variables & Constants Printing Optional Switch Tuple Function Protocol

Page 5: Guide for Swift and Viewer app

Variables & Constants

Use `let` for constants and `var` for variables in Swift.

var str1 = "Hello" // Variable str1 = "Test"

let str2 = "World" // Constant str2 = "Test" // Compile error

var arr1: [Int] = [] // Mutable array arr1.append(1)

let arr2: [Int] = [] // Immutable array arr2.append(1) // Compile error

Page 6: Guide for Swift and Viewer app

Printing

Use `println()` method. It’s very similar to `NSLog()`.

Values of variables are expandable inside String using `\()` like below.

let hello = "Hello" let world = "世界" println("\(hello), \(world)") // Hello, 世界

Page 7: Guide for Swift and Viewer app

Optional

To indicate it might be `nil`. Use `Optional<T>` to declare a variable, method as optional. var str3: Optional<String> = nil var str4: String? = nil // Syntax Sugar

Page 8: Guide for Swift and Viewer app

Optional

`Optional<T>` is kind of `enum`. enum Optional<T> : Reflectable, NilLiteralConvertible { case None case Some(T) /// Construct a `nil` instance. init() /// Construct a non-\ `nil` instance that stores `some`. init(_ some: T)

So, it is able to express them. var str5: Optional<String> = Optional<String>() // nil var str6: Optional<String> = Optional<String>("Hello") // {Some "Hello"}

Page 9: Guide for Swift and Viewer app

Optional

Use `!` mark after an optional value to force the unwrapping of its value. var str6: Optional<String> = Optional<String>("Hello") var str7: String = str6 // Compile error var str8: String = str6! // "Hello"

An optional value fails when the optional is `nil` var str5: Optional<String> = Optional<String>() var str9: String = str5! // Runtime error

Page 10: Guide for Swift and Viewer app

Switch

The `Switch` statement is like other languages.

Different points in Swift Not only Integers. Coverage case, all possible values. `break` statement is NOT required. Use `fallthrough` to go through explicitly

Adding other conditional statement

Page 11: Guide for Swift and Viewer app

Switch

let country: String = "Canada" switch country { case "USA": println("USA") case "Canada": println("Canada") case "Japan": fallthrough default: println("Other") }

Page 12: Guide for Swift and Viewer app

Tuple

var p: (x: Float, y: Float) = (1.0, 2.0) println("x: \(p.0), y: \(p.1)") println("x: \(p.x), y: \(p.y)")

Page 13: Guide for Swift and Viewer app

Tuple with Switch

var p: (x: Float, y: Float) = (1.0, 2.0) println("x: \(p.0), y: \(p.1)") println("x: \(p.x), y: \(p.y)")

switch p { case (0, 0): println("Point is on origin.") case (_, 0): println("Point is on X-axes.") case (0, _): println("Point is on Y-axes.") case (-2...2, -2...2): println("Point is inside the box") default: println("Point is outside the box") }

Page 14: Guide for Swift and Viewer app

Function

Use `func` keyword to declare. func myPow(x: Int, e: Int) -> Int { var d = 1 for _ in 1...e { d *= x } return d } myPow(3, 4)

``` func [Name](arg1: Type1,..) -> [Return Type] { // Procedure } ```

Page 15: Guide for Swift and Viewer app

Protocol

protocol AProtocol { func behaviour() -> Void }

@objc protocol SomeProtocol { func behaviour() -> Void optional func optBehaviour() -> Void }

class Foo: NSObject, AProtocol { func behaviour() { } }

Page 16: Guide for Swift and Viewer app

Protocolの詳しくは

後ほど話します

Page 17: Guide for Swift and Viewer app

Viewer App

Page 18: Guide for Swift and Viewer app

Viewer App

InstagramのAPIを使った簡単なViewerアプリ開発

0. アプリモック 1. 開発準備 2. 実装 3. おわりに

Page 19: Guide for Swift and Viewer app

0. アプリモック

Page 20: Guide for Swift and Viewer app
Page 21: Guide for Swift and Viewer app

Specification

テーブルビューにInstagramの情報を並べる →InstagramAPI連携が必要

あるセルをタップすると、詳細のビューへ遷移する →GoogleAnalyticsを使ってViewのトラッキングをしたい

Page 22: Guide for Swift and Viewer app

1. 開発準備

Instagram APIについて フレームワーク/ライブラリの導入

Page 23: Guide for Swift and Viewer app

Instagram APIについて

Page 24: Guide for Swift and Viewer app

Instagram APIについて

今回使用するエンドポイント https://api.instagram.com/v1/media/popular?client_id=CLIENT-ID

CLIENT-IDの発行 => InstagramへClient登録 http://instagram.com/developer/clients/register/

※注: 1時間につき5000リクエスト 詳細:Instagram API Endpoints http://instagram.com/developer/endpoints/

Page 25: Guide for Swift and Viewer app

Instagram APIについて

今回使用するエンドポイント https://api.instagram.com/v1/media/popular?client_id=CLIENT-ID

人気のメディア(写真/動画)データが返却される 画像についての情報取得は下記のように行える

responseJSON["data"][i]["images"]["standard_resolution"] /* { "url": "http://s.cdninstagram.com/hphotos-xap1/a.jpg", "width": 640, "height": 640 } */

Page 26: Guide for Swift and Viewer app

フレームワーク/ライブラリの導入

Alamofire SwiftyJSON GoogleAnalytics

Page 27: Guide for Swift and Viewer app

フレームワークの導入

今回使用するフレームワーク

Alamofire (https://github.com/Alamofire/Alamofire) ネットワーク通信を手軽にする

SwiftyJSON (https://github.com/SwiftyJSON/SwiftyJSON) JSONデータを構造体で扱える

この2つをダウンロードかGit-Submoduleでプロジェクトフォルダへ ※今回、ライブラリ管理ツールは使用しません

Page 28: Guide for Swift and Viewer app

プロジェクトへ追加

フレームワークのプロジェクトを作成しているプロジェクトへ追加

Page 29: Guide for Swift and Viewer app

ターゲットへ追加

ターゲットにフレームワークを追加 →ターゲット選択 →General選択 →Embedded Binariesの「+」 →追加したいフレームワーク選択

最近のXcodeならここへ追加するとBuild Phasesへ適切に設定される

Page 30: Guide for Swift and Viewer app
Page 31: Guide for Swift and Viewer app

フレームワークの導入

フレームワークの準備は完了

あとは使用するコードにimportするだけです

import Alamofire import SwiftyJSON

Page 32: Guide for Swift and Viewer app

ライブラリの導入

今回使用するライブラリ(Non-フレームワーク形式)

GoogleAnalytics https://developers.google.com/analytics/devguides/collection/ios/v3/

CocoaPodsを使って導入します gemからcocoapodsをインストール(最新は0.35.0)

Page 33: Guide for Swift and Viewer app

CocoaPods - Podfile

CocoaPodsの管理ファイル<Podfile>を作成する

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '8.0' inhibit_all_warnings!

xcodeproj 'InstagramViewer'

link_with 'InstagramViewer' pod 'GoogleAnalytics-iOS-SDK', '3.0.9'

Page 34: Guide for Swift and Viewer app

pod install -> xcworkspace

ターミナル上で`pod install`を行いライブラリを取得する pod install

`pod install`後からはxcworkspaceファイルを開く CocoaPods側でPodsプロジェクトが作成されるため\

xcodeproj: プロジェクト単体を管理 xcworkspace: xcodeprojプロジェクトを複数管理

Page 35: Guide for Swift and Viewer app

ブリッジングヘッダー

SwiftでObjective-Cクラスを使用するにはブリッジングが必要 ブリッジングヘッダーファイルを定義する

#ifndef InstagramViewer_Objective_C_Bridging_Header #define InstagramViewer_Objective_C_Bridging_Header #import <GoogleAnalytics-iOS-SDK/GAI.h> #import <GoogleAnalytics-iOS-SDK/GAIFields.h> #import <GoogleAnalytics-iOS-SDK/GAILogger.h> #import <GoogleAnalytics-iOS-SDK/GAIDictionaryBuilder.h> #endif /* InstagramViewer_Objective_C_Bridging_Header */

※参考:http://qiita.com/kaneshinth/items/71f1c19d094e87e30a07

Page 36: Guide for Swift and Viewer app

ブリッジングヘッダー

Page 37: Guide for Swift and Viewer app

2. 実装

実装パターン Storyboard Instagram API → データ → TableView Google Analytics

Page 38: Guide for Swift and Viewer app

実装パターン

Page 39: Guide for Swift and Viewer app

実装パターン(一例)

├── InstagramViewer │   ├── AppDelegate.swift │   ├── Application │   │   ├── Controllers // ViewController │   │   │   ├── DetailViewController.swift │   │   │   └── MediaListController.swift │   │   ├── Models // Logic layer │   │   │   └── MediaModel.swift │   │   ├── Requests // API Request │   │   │   └── MediaRequest.swift │   │   ├── Utilities // Utility │   │   │   └── AnalyticsUtils.swift │   │   └── Views // View, Cell │   │   └── MediaListCell.swift │   └── Objective-C-Bridging-Header.h

Page 40: Guide for Swift and Viewer app

実装パターン

実装パターン(グループ構成) アプリケーションの規模 使用するデザインパターン Observer Pattern, Reactive Pattern, MVVM, …

Page 41: Guide for Swift and Viewer app

Storyboard

Page 42: Guide for Swift and Viewer app

Main.storyboard

Page 43: Guide for Swift and Viewer app

Initial View Controller

UINavigationController ViewControllerをチェーンで管理

Page 44: Guide for Swift and Viewer app

MediaListController

UITableViewController Cellはデフォルトで使用する CellにDetailViewControllerへのSegueを設定している 

Page 45: Guide for Swift and Viewer app

DetailViewController

UIViewController UIImageViewを設置

Page 46: Guide for Swift and Viewer app

Segue

Segueをフックしたい場合 Segue Identifier

Segueの遷移元、遷移先を設定するときに指定

Page 47: Guide for Swift and Viewer app

Instagram API → データ

Page 48: Guide for Swift and Viewer app

Instagram API → データ

データ取得の流れ

1. Alamofire.Requestクラスを使用してInstagram APIへリクエスト →MediaRequestクラス

2. レスポンスのデータをSwiftJSON.JSON構造体へ →MediaRequestクラス

3. Media構造体に必要な情報をJSON構造体から取得 →MediaModelクラス

Page 49: Guide for Swift and Viewer app

Instagram APIとの連携

JSONMedia

Media

Media

Instagram APIApp

Swifty JSON

Swifty JSON

Alamofire

Page 50: Guide for Swift and Viewer app

MediaRequestクラス

1. Alamofire.Requestクラスを使用してInstagram APIへリクエスト → requestメソッドが用意されているので、それを使用する

let urlString = "https://api.instagram.com/v1/media/popular" let param = ["client_id": "<#CLIENT-ID#>"] let req = request(.GET, urlString, parameters: param)

Page 51: Guide for Swift and Viewer app

MediaRequestクラス

Instagram APIApp

JSONMedia

Media

Media

Swifty JSON

Swifty JSON

Alamofire

Page 52: Guide for Swift and Viewer app

MediaRequestクラス

2. レスポンスのデータをSwiftJSON.JSON構造体へ → Requestの拡張にresponseメソッドがあるのでこれを使用する

let urlString = "https://api.instagram.com/v1/media/popular" let param = ["client_id": "<#CLIENT-ID#>"] let req = request(.GET, urlString, parameters: param) req.response { (request, response, responseData, error) -> Void in if error == nil { if let data = responseData as? NSData { let json = JSON(data: data) // …… } } }

SwiftyJSON.JSON

Page 53: Guide for Swift and Viewer app

MediaModelクラス

Media

Media

MediaSwifty JSON

Instagram APIApp

JSON

Swifty JSON

Alamofire

Page 54: Guide for Swift and Viewer app

MediaModelクラス

3. Media構造体に必要な情報をJSON構造体から取得 → まず、Media構造体を作成

struct Caption { var username: String? var text: String? }

struct Media { var thumbnailURL: NSURL? var imageURL: NSURL? var caption: Caption? }

Page 55: Guide for Swift and Viewer app

MediaModelクラス

Media

Media

Media

Instagram APIApp

Swifty JSON

JSON

Swifty JSON

Alamofire

Page 56: Guide for Swift and Viewer app

MediaRequestクラス

3. Media構造体に必要な情報をJSON構造体から取得 → JSON構造体からMedia構造体へ let json = JSON(data: data) if let array = json["data"].array { array.map({ (elm: JSON) -> Void in var caption = Caption( username: elm["caption"]["from"]["username"].string, text: elm["caption"]["text"].string) var media = Media( thumbnailURL: elm["images"]["thumbnail"]["url"].URL, imageURL: elm["images"]["standard_resolution"]["url"].URL, caption: caption) self.mediaList.append(media) }) }

Page 57: Guide for Swift and Viewer app

SwiftyJSONを

使わない場合

Page 58: Guide for Swift and Viewer app

Instagramから情報を取得

req.responseJSON { (request, response, jsonData, error) -> Void in if error == nil { if let json = jsonData as? NSDictionary { self.mediaList = [] if let array = json["data"] as? NSArray { for d in array { if let dict = d as? NSDictionary { var caption = Caption( username: ((dict["caption"] as? NSDictionary)?["from"] as? NSDictionary)?["username"] as? NSString, text: (dict["caption"] as? NSDictionary)?["text"] as NSString ) var media = Media( thumbnailURL: NSURL(string: ((dict["images"] as? NSDictionary)?["thumbnail"] as? NSDictionary)?["url"] as NSString)!, imageURL: NSURL(string: ((dict["images"] as? NSDictionary)?["standard_resolution"] as? NSDictionary)?["url"] as NSString)!, caption: caption ) }}}}}}

Page 59: Guide for Swift and Viewer app

Downcastを

多用する必要がある

(foo as? Type)

Page 60: Guide for Swift and Viewer app

データ → UITableView

Page 61: Guide for Swift and Viewer app

UITableView

UITableViewにて、テーブルを表示させる

UITableViewDataSource Protocol テーブル表示の構成に必要なデータを取得する UITableViewDelegate Protocol テーブルのビューやアクションをフックする

UITableViewDataSourceとUITableViewDelegateを混在しないように

Page 62: Guide for Swift and Viewer app

UITableViewDataSource

下記は必ず実装する必要がある

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

Page 63: Guide for Swift and Viewer app

UITableViewDelegate

セルの高さ セルの表示やタップ時の挙動 セルの編集挙動 セクションヘッダー/フッタービューの高さ セクションヘッダー/フッタービューのビュー

Page 64: Guide for Swift and Viewer app

MediaをUITableViewへ

Mediaを表示するには… セルの数 →MediaListの中にあるmediaの数

セルの高さ(不変?可変?) →可変ならばmediaから計算

セルの中身 →mediaから必要な情報を取得

今回のプロジェクトではInstagram APIから取得したデータはMediaModelが内部で保持している

Page 65: Guide for Swift and Viewer app

UITableView - セルの数

MediaModelが保持している配列の中身の数

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return MediaModel.sharedInstance.mediaList.count }

Page 66: Guide for Swift and Viewer app

UITableView - セルの高さ

可変のときを考えて、MediaListCellクラスからセルの高さを取得する

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let media = MediaModel.sharedInstance.mediaList[indexPath.row] return MediaListCell.heightForRow(media) }

// === // MediaListCell.swift内部 class func heightForRow(media: Media) -> CGFloat { return 100.0 }

Page 67: Guide for Swift and Viewer app

UITableView - セルの中身

"疎"にするため、MediaListCellのインスタンスへmediaを渡す

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as MediaListCell let media = MediaModel.sharedInstance.mediaList[indexPath.row] return cell.configure(media) }

Page 68: Guide for Swift and Viewer app

MediaListCell - configure

画像データは非同期の別スレッドで取得する 取得完了したら、同期でメインスレッドに戻る func configure(media: Media) -> MediaListCell { self.textLabel?.text = media.caption?.text dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { let data = NSData(contentsOfURL: media.thumbnailURL!) dispatch_sync(dispatch_get_main_queue(), { self.imageView!.image = UIImage(data: data!, scale: UIScreen.mainScreen().scale) self.setNeedsLayout() }) }) return self }

Page 69: Guide for Swift and Viewer app

Google Analytics

Page 70: Guide for Swift and Viewer app

Google Analytics の初期化

初期化はGAIインスタンスから`GAITracker`を取得する

var tracker: GAITracker? class func setupGoogleAnalytics() { GAI.sharedInstance().trackUncaughtExceptions = true; GAI.sharedInstance().dispatchInterval = 20 GAI.sharedInstance().logger.logLevel = .Verbose AnalyticsUtils.sharedInstance.tracker = GAI.sharedInstance().trackerWithTrackingId("UA-XXXXXXXX-X") }

Page 71: Guide for Swift and Viewer app

Google Analytics へ送信

GoogleAnalyticsへ情報を送信 `GAITracker`の`send`メソッド

class func trackView(screenName: String) { if let tracker = AnalyticsUtils.sharedInstance.tracker { let build = GAIDictionaryBuilder.createAppView() .set(screenName, forKey: kGAIScreenName).build() AnalyticsUtils.sharedInstance.tracker?.send(build) } }

Page 72: Guide for Swift and Viewer app

ViewWillAppearでトラッキング

作った`trackView`メソッドを必要なときに使用する

override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) let screenName = reflect(self).summary AnalyticsUtils.trackView(screenName) }

Page 73: Guide for Swift and Viewer app

おわりに

Page 74: Guide for Swift and Viewer app

時間があれば

聞きたいことをライブコーディングします - Delegateとか