タコさんブログ

プログラミングメモと小言

ストーリーボードにナビゲーションコントローラ・タブバーコントローラを素早く追加するTips

ナビゲーションコントローラ・タブバーコントローラをStoryboardに追加するとき 右側のUtilityペインの Object Library からドラッグ&ドロップするとナビゲーションコントローラ・タブバーコントローラ以外にもテンプレートのビューコントローラが付いてきて、それらを削除してから使いたいビューコントローラにセグエを繋ぐとういことをしないとけいない。

f:id:tiny_wing:20150823224258p:plain

  • 不要なテンプレートビューコントローラを削除

f:id:tiny_wing:20150823224517p:plain

  • ルートにしたいビューコントローラにセグエを繋げて完成[画像1]

f:id:tiny_wing:20150823225401p:plain

これ面倒ですよね?!

ということで、使いたいビューコントローラを一気にナビゲーションコントローラ・ タブバーコントローラに 埋め込む(Embedする)方法 を紹介します。

Storyboard に UINavigationController を追加するTip

  1. ナビゲーションコントローラのルートにしたいビューコントローラを選択
  2. Xcodeのメニューの Editor を選択
  3. Embed In
  4. Navigation Controller を選択 (ヴューコントローラを選択していないと選択できない)
  5. 画像1が出来上がる

f:id:tiny_wing:20150823224600p:plain

Storyboard に UITabBarController を追加するTip

タブバーコントローラの追加もナビゲーションの追加と同様に

  1. タブバーコントローラの子ヴューコントローラにしたいヴューコントローラを選択
  2. Xcodeのメニューの Editor を選択
  3. Embed In
  4. Tab Bar Controller を選択 (ヴューコントローラを選択していないと選択できない)
  5. 画像1のタブバーコントローラ版が出来上がる

swift の as as! as? オペレータ (Type Cast Operator) と is (Type Check Operator) とパターンマッチ

環境

as オペレータ

  • Swfitコンパイラにより型変換・キャストが成功すると保証されるときに使用
  • アップキャスト
  • リテラルの型を指定
class Animal {}
class Dog: Animal {}

let dog = Dog()

let animal = dog as Animal // アップキャスト

let floatValue = 1 as Float  // 型指定

as! オペレータ

  • 強制的にダウンキャスト
  • ダウンキャストが成功することが分かっている場合に使用
  • ダウンキャストが失敗するとruntimeエラーになる
let animal1: Animal = Dog()
let dog = animal1 as! Dog // OK

let animal2 = Animal()
let dog2 = animal as! Dog // ダウンキャストできないのでruntimeエラー

as? オペレータ

  • ダウンキャストが成功するか分からない場合に使用
  • 戻り値はオプショナル型
  • 失敗した場合はOptional.None
let animal1: Animal = Dog()
let dog = animal1 as? Dog // dogの型はOptional<Dog>

let animal2 = Animal()
let dog2 = animal as? Dog // エラーは起こらない。dog2の中身は.None

if let dog = dog2 {
    print("dog is not nil")
} else {
    print("dog is nil")
}  // 出力: dog is nil

is 型チェックオペレータ

  • isオペレータはインスタンスがある型のサブクラスの型か判定するときに使用
  • 戻り値はある型のサブクラスの型のとき true, そうでないとき false
let animal = Animal()
let isDog = animal is Dog
print(isDog)  // 出力: false

let dog = Dog()
let isAnimal = dog is Animal
print(isAnimal) // 出力: true

// AnyObject型にしないとコンパイラがSmartすぎて
// 下の警告がでるためAnyObjectにしている:
// 'is' test is always true
let isDog2: AnyObject = dog is Dog
print(isDog2) // 出力: true

as と is パターンマッチ (Type-Casting Patterns)

ややこしことに is, as はswitchのパターンマッチと一緒に使用できる。

class Animal {
  func bark() -> String {
      return "$&%#"
  }
}

class Dog: Animal {
  override func bark() -> String {
    return "woof-woof"
  }
}

let things: [Any] = [0, 0.0, Animal(), Dog()]

for thing in things {
  switch thing {
    // thingがIntか判定。値自体に興味はないときに is を使用する
    case is Int:
      print("thing is Int value")
    case is Float:
      print("thing is Float")
    // thingがDogにマッチするか判定し
    // (asの右側のタイプかそのサブタイプか)、
    // マッチした場合、Dogにキャストされたdogを取得
    case let dog as Dog:
      print("Dog barks " + dog.bark())
    case let animal as Animal:
      print("Animal barks " + animal.bark())
    default:
      print("something else")
  }
}

/*
出力:
  thing is Int value
  thing is Float
  Animal barks $&%#
  Dog barks woof-woof
*/

Swiftで作ったアプリをリリースしました!!

Swiftで作ったカジュアルゲームです!

土日を使って少しずつ作りました。 モチベーションが上がらないときはコワーキングスペースに行ってみたりして作業しましたが、 基本的には引きこもって作業。

Swift良いですね!

Tap Speed

Tap Speed

  • okuhiro ohmuta
  • ゲーム
  • 無料

Swift 2.0 の indirect で2分探索木(Binary search tree)を試す

indirectが使用できる前はBoxクラスなんかを使って再帰的なenumデータ型 を実装していたみたいだが、indirectを使えばBoxクラス等を使わずにシンプルにList, Tree なんかを書ける。

環境

2分探索木

元ネタは すごいHaskellたのしく学ぼう!第7章の再帰的なデータ構造

indirectで木を定義

indirect enum Tree<T> {
    case EmptyTree
    case Node(T, left: Tree<T>, right: Tree<T>)
}

要素と2つの空部分木からなるノードを作る関数

func singleton<T>(a: T) -> Tree<T> {
    return .Node(a, left: .EmptyTree, right: .EmptyTree)
}

2分探索木に要素を挿入する関数

func treeInsert<T: Comparable>(x: T, tree: Tree<T>) -> Tree<T> {
    switch tree {
    case .EmptyTree:
        return singleton(x)

    case let .Node(a, left: left, right: right):
        if x == a {
            return .Node(a, left: left, right: right)
        } else if x < a {
            return .Node(a, left: treeInsert(x, tree: left), right: right)
        } else {
            return .Node(a, left: left, right: treeInsert(x, tree: right))
        }
    }
}

Google Analytics for iOS with Swift

CocoaPodsを使用しないで、Google Analyticsを入れる方法

手動でGoogle Analyticsのスクリーン計測を行うまでをメモしておく。

環境

準備

  1. Google Developers Download the SDKからからzipをダウンロードする。現時点でのバージョンは3.15

  2. GoogleAnalytics/Library配下のファイルをプロジェクトに追加する

    • GAI.h
    • GAIDictionaryBuilder.h
    • GAIEcommerceProduct.h
    • GAIEcommerceProductAction.h
    • GAIEcommercePromotion.h
    • GAIFields.h
    • GAILogger.h
    • GAITrackedViewController.h
    • GAITracker.h
  3. Build PhasesのLink Binary With Librariesに以下を追加

    • CoreData.framework
    • SystemConfiguration.framework
    • libz.tbd (Xcode 6.4 の時 libz.dylib)
    • libsqlite3.tbd (Xcode 6.4 の時 libsqlite3.dylib)
    • libGoogleAnalyticsServices.a (zip内)
  4. Bridging headerを作成する(ex. BridgingHeader.h)

  5. Build SettingsのObjective-C Bridging Headerに${PRODUCT_NAME}/BridgingHeader.hを記述

  6. BridgingHeader.hに以下のインポート文を追加

// とりあえずLibrary配下のヘッダーをインポートする
#import "GAI.h"
#import "GAIDictionaryBuilder.h"
#import "GAIEcommerceProduct.h"
#import "GAIEcommerceProductAction.h"
#import "GAIEcommercePromotion.h"
#import "GAIFields.h"
#import "GAILogger.h"
#import "GAITrackedViewController.h"
#import "GAITracker.h"


'GAI.h' file not found とBridgingHeaderでエラーがでるときは、Header Search Pathが設定されているか確認する。Build Settings -> Search Path - Header Search Paths 。設定されていない場合は、パスを追加 (例 $(SRCROOT)/** )。

※ #import <Google/Analytics.h>ができるのはPodからインストールしたときだけなのかな?

ラッキングIDを設定する

計測を行う前にトラッキングIDを設定する

GAI.sharedInstance().trackerWithTrackingId("UA-xxxxxx-x")

GoogleService-Info.plistを使用する場合は下でもOKなのかな?

var configureError:NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError)")

手動でスクリーン計測を行う(Automatic Screen Measurement)

var tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIScreenName, value: "Home Screen") // 計測するスクリーン名を記述する
var params = GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject]
tracker.send(params)

SwiftのRangeからArrayを取得する方法

環境

方法その1

mapを使う

let range = Range(start: 0, end: 10)
let array = range.map{$0}
println(array) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

方法その2

Array#initから生成する

let range = Range(start: 0, end: 10)
let array = Array(range)
println(array) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

カスタム画面遷移でモーダル表示をしたとき、dismissしたら画面が無くなっている!?

環境

現象

プログラムの内容:カスタム画面遷移でモーダル画面を表示させて、モーダル画面内のボタンが押されたら dismissViewControllerAnimated でカスタム画面遷移でモーダル画面を閉じる。

これをしたら、こんな感じです。

f:id:tiny_wing:20150607214006g:plain

dismissを押して、モーダル画面を閉じたら、遷移後の画面に何も表示されていません。 なぜや!!

バグか?

いつも通りググったら、ここに辿り着きました。

I was having the same problem here – looks like a bug in iOS 8

iOS 8のバグらしい。 UIWindowのビュー階層が完全に空になっている。

Open Radarにもバグとして報告してあるようだ。

回避策

これを回避するには animateTransitionメソッドのアニメーション終了後に、 toViewController.view をkeyWindowにaddSubviewする。

今回のコード的には下のようにする。

@objc func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
     // From ViewController
     let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
        
     // To ViewController
     let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
        
     let fromView = fromVC!.view
     let toView = toVC!.view
        
     // アニメーションを実行するためのコンテナビューを取得
     let containerView = transitionContext.containerView()
        
     containerView.insertSubview(toView, belowSubview: fromView)
            
     UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: .CurveLinear,
          animations: {
               fromView.transform = CGAffineTransformMakeScale(0.5, 0.5)
          },
          completion: {(finished: Bool) in
               let completed = !transitionContext.transitionWasCancelled()
               transitionContext.completeTransition(completed)
               // これを追加!!!
               UIApplication.sharedApplication().keyWindow!.addSubview(toView)
          })
}

これで下のように表示されるようになりました。

f:id:tiny_wing:20150607220210g:plain

今回のフルソースはこちらにあります。