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

Guide for Swift and Viewer app

Jul 12, 2015

Download

Engineering

Shintaro Kaneko
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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とか