Upload
shintaro-kaneko
View
1.170
Download
3
Embed Size (px)
Citation preview
#kaneshin
© 2014 Shintaro Kaneko
Guide for Swift
and Viewer App
Shintaro Kaneko iOS/Android/Web Developer
Agenda
Guide for Swift Viewer App
Guide for Swift
Guide for Swift
Variables & Constants Printing Optional Switch Tuple Function Protocol
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
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, 世界
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
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"}
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
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
Switch
let country: String = "Canada" switch country { case "USA": println("USA") case "Canada": println("Canada") case "Japan": fallthrough default: println("Other") }
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)")
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") }
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 } ```
Protocol
protocol AProtocol { func behaviour() -> Void }
@objc protocol SomeProtocol { func behaviour() -> Void optional func optBehaviour() -> Void }
class Foo: NSObject, AProtocol { func behaviour() { } }
Protocolの詳しくは
後ほど話します
Viewer App
Viewer App
InstagramのAPIを使った簡単なViewerアプリ開発
0. アプリモック 1. 開発準備 2. 実装 3. おわりに
0. アプリモック
Specification
テーブルビューにInstagramの情報を並べる →InstagramAPI連携が必要
あるセルをタップすると、詳細のビューへ遷移する →GoogleAnalyticsを使ってViewのトラッキングをしたい
1. 開発準備
Instagram APIについて フレームワーク/ライブラリの導入
Instagram APIについて
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/
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 } */
フレームワーク/ライブラリの導入
Alamofire SwiftyJSON GoogleAnalytics
フレームワークの導入
今回使用するフレームワーク
Alamofire (https://github.com/Alamofire/Alamofire) ネットワーク通信を手軽にする
SwiftyJSON (https://github.com/SwiftyJSON/SwiftyJSON) JSONデータを構造体で扱える
この2つをダウンロードかGit-Submoduleでプロジェクトフォルダへ ※今回、ライブラリ管理ツールは使用しません
プロジェクトへ追加
フレームワークのプロジェクトを作成しているプロジェクトへ追加
ターゲットへ追加
ターゲットにフレームワークを追加 →ターゲット選択 →General選択 →Embedded Binariesの「+」 →追加したいフレームワーク選択
最近のXcodeならここへ追加するとBuild Phasesへ適切に設定される
フレームワークの導入
フレームワークの準備は完了
あとは使用するコードにimportするだけです
import Alamofire import SwiftyJSON
ライブラリの導入
今回使用するライブラリ(Non-フレームワーク形式)
GoogleAnalytics https://developers.google.com/analytics/devguides/collection/ios/v3/
CocoaPodsを使って導入します gemからcocoapodsをインストール(最新は0.35.0)
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'
pod install -> xcworkspace
ターミナル上で`pod install`を行いライブラリを取得する pod install
`pod install`後からはxcworkspaceファイルを開く CocoaPods側でPodsプロジェクトが作成されるため\
xcodeproj: プロジェクト単体を管理 xcworkspace: xcodeprojプロジェクトを複数管理
ブリッジングヘッダー
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
ブリッジングヘッダー
2. 実装
実装パターン Storyboard Instagram API → データ → TableView Google Analytics
実装パターン
実装パターン(一例)
├── 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
実装パターン
実装パターン(グループ構成) アプリケーションの規模 使用するデザインパターン Observer Pattern, Reactive Pattern, MVVM, …
Storyboard
Main.storyboard
Initial View Controller
UINavigationController ViewControllerをチェーンで管理
MediaListController
UITableViewController Cellはデフォルトで使用する CellにDetailViewControllerへのSegueを設定している
DetailViewController
UIViewController UIImageViewを設置
Segue
Segueをフックしたい場合 Segue Identifier
Segueの遷移元、遷移先を設定するときに指定
Instagram API → データ
Instagram API → データ
データ取得の流れ
1. Alamofire.Requestクラスを使用してInstagram APIへリクエスト →MediaRequestクラス
2. レスポンスのデータをSwiftJSON.JSON構造体へ →MediaRequestクラス
3. Media構造体に必要な情報をJSON構造体から取得 →MediaModelクラス
Instagram APIとの連携
JSONMedia
Media
Media
Instagram APIApp
Swifty JSON
Swifty JSON
Alamofire
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)
MediaRequestクラス
Instagram APIApp
JSONMedia
Media
Media
Swifty JSON
Swifty JSON
Alamofire
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
MediaModelクラス
Media
Media
MediaSwifty JSON
Instagram APIApp
JSON
Swifty JSON
Alamofire
MediaModelクラス
3. Media構造体に必要な情報をJSON構造体から取得 → まず、Media構造体を作成
struct Caption { var username: String? var text: String? }
struct Media { var thumbnailURL: NSURL? var imageURL: NSURL? var caption: Caption? }
MediaModelクラス
Media
Media
Media
Instagram APIApp
Swifty JSON
JSON
Swifty JSON
Alamofire
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) }) }
SwiftyJSONを
使わない場合
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 ) }}}}}}
Downcastを
多用する必要がある
(foo as? Type)
データ → UITableView
UITableView
UITableViewにて、テーブルを表示させる
UITableViewDataSource Protocol テーブル表示の構成に必要なデータを取得する UITableViewDelegate Protocol テーブルのビューやアクションをフックする
UITableViewDataSourceとUITableViewDelegateを混在しないように
UITableViewDataSource
下記は必ず実装する必要がある
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
UITableViewDelegate
セルの高さ セルの表示やタップ時の挙動 セルの編集挙動 セクションヘッダー/フッタービューの高さ セクションヘッダー/フッタービューのビュー
MediaをUITableViewへ
Mediaを表示するには… セルの数 →MediaListの中にあるmediaの数
セルの高さ(不変?可変?) →可変ならばmediaから計算
セルの中身 →mediaから必要な情報を取得
今回のプロジェクトではInstagram APIから取得したデータはMediaModelが内部で保持している
UITableView - セルの数
MediaModelが保持している配列の中身の数
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return MediaModel.sharedInstance.mediaList.count }
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 }
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) }
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 }
Google Analytics
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") }
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) } }
ViewWillAppearでトラッキング
作った`trackView`メソッドを必要なときに使用する
override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) let screenName = reflect(self).summary AnalyticsUtils.trackView(screenName) }
おわりに
時間があれば
聞きたいことをライブコーディングします - Delegateとか