20190411のSwiftに関する記事は8件です。

FirebaseとiAd.frameworkの連携についてわかったことメモ

やりたいこと

BigQuery内でSearch Adsのコンバージョンを集計させたい

FirebaseのApple Search Adsに関するドキュメント

Firebaseの集計データはBigQueryのデータをいい感じに集計して表示してくれている。Google広告と連携させていれば、どの広告をタップしてインストールしてくれたのかということがわかる。しかしAppleのSearch Adsに関しては、iOSアプリ側から何かしら入れてあげないと集計できていないことがわかった。

具体的に送られるイベントや、何が送られるのかはこちらを参照する
https://support.google.com/firebase/answer/6317518?hl=ja

Apple Search Adsに関して引用すると

Apple Search Ads
Apple Search Ads のクリックによってアプリがインストールされた場合、firebase_campaign イベントが上記のパラメータとともに記録され、キャンペーン関連のイベントについては次のデータが表示されます。

参照元 = Apple
メディア = search
キャンペーン = <iad キャンペーン名>
Apple Search Ads をトラッキングするには、アプリ用の Xcodeプロジェクトファイルに iAd フレーム ワークを追加する必要があります。

このようにAppleに関しても集計できそうということがわかる
iAd.frameworkを入れればFirebaseがいい感じに集計してくれそうということがわかる

BigQueryの調べ方

具体的にBigQueryに何が送られてくるのかは、こちらを参照する
https://support.google.com/firebase/answer/7029846?hl=ja&ref_topic=7029512

traffic_sourceを調べる具体例

traffic_sourceとは ユーザーを最初に獲得したトラフィック ソースの名前。このフィールドは当日テーブルでは使用されません。

叩くクエリ例

SELECT traffic_source.source,  COUNT(*) 
FROM `{テーブル名}` 
GROUP BY  traffic_source.source
LIMIT 100

下記のような感じで、データの結果が帰ってくる。この例では、テーブルを検索し、どの流入元が一番多いのか調べるために、数を数えた
おそらくSearch Adsで広告を打った場合、traffic_source.sourceAppleと入ってきてくれるはず

スクリーンショット 2019-03-04 17.41.43.png

スクリーンショット 2019-03-05 17.28.35.png

iAd.framework 導入について

iAd.frameworkのドキュメントには下記のようにフレームワークを入れると書いてある

iAd framework
The iAd framework is bundled with Xcode. Follow these steps to add the iAd framework to the Xcode project file for your app:
1. Go to your target view and select General.
2. Scroll down to a section called Linked Frameworks and Libraries and click the plus (+) icon.
3. In the dropdown menu, search for iAd.
4. Select iAd.framework and click the Add button.

AppleのiAd.frameworkのドキュメント

ドキュメントに従いtargetの Generalを選択し、Linked Frameworks and Librariesの項目の下の方のプラスボタンを押して,iAd.frameworkを導入する

問題点

iAd.frameworkを導入するだけで、本当にFirebaseが集計してくれるのか不安、Search Adsに課金しない限り調べる方法はないのか探していた

改善策

Xcode デバックコンソールでログを見る

image.png
image.png

ドキュメントリンク

https://firebase.google.com/docs/analytics/ios/events?hl=ja

Firebase Debug Viewで送信されている
イベントを確認する

image.png

気になるログを発見した

iAd.frameworkを導入するとログが変わっていることがわかった

導入前

xxxxApp[00000:000000]  Ad framework is not linked. Search Ad Attribution Reporter is disabled.
xxxxApp[00000:000000]  No data to upload. Upload task will not be scheduled
xxxxApp[00000:000000]  Analytics enabled

導入後

xxxxApp[00000:000000]  Scheduling Search Ad Report timer
xxxxApp[00000:000000]  Search Ad campaign report alarm scheduled to fire in approx. (s):

アプリ流入比率変えた.032.jpeg

結果

無事にFirebaseで送られてBigQuery内で確認することができた

アプリ流入比率変えた.034.jpeg

(おまけ)iAd.framework 導入によってアプリ内に、端末がどのキャンペーン経由でインストールされたのか取得する

Firebaseの連携とは全く関係ないのだが一応、必要な人もいるかも知れないので共有
iAd.frameworkを導入すると利用できるようになる関数ADClient.shared().requestAttributionDetailsを用いて,
辞書 attributionDetails の中に広告の情報が入っている。ここでとりだして、各自データを集計しているサーバーにおくればいいのではないかと思う

詳細に関してはAppleのiAd.frameworkのドキュメントに書いてある

import iAd
import UIKit

class TestViewController: UIViewController{

override func viewDidLoad() {
        super.viewDidLoad()
    ADClient.shared().requestAttributionDetails({ (attributionDetails, error) in

            if error == nil {
                for (type, adDictionary) in attributionDetails! {
                    var attribution = adDictionary as? Dictionary<AnyHashable, Any>;
                    let params = [
                        "appID": "self.appData.appID",
                        "iadAdgroupId": attribution?["iad-adgroup-id"] as? String as Any,
                        "iadAdgroupName": attribution?["iad-adgroup-name"] as? String as Any,
                        "iadAttribution": attribution?["iad-attribution"] as? String  as Any,
                        "iadCampaignId": attribution?["iad-campaign-id"] as? String as Any,
                        "iadCampaignName": attribution?["iad-campaign-name"] as? String as Any,
                        "iadClickDate": attribution?["iad-click-date"] as? String as Any,
                        "iadConversionDate": attribution?["iad-conversion-date"] as? String as Any,
                        "iadCreativeId": attribution?["iad-creative-id"] as? String as Any,
                        "iadCreativeName": attribution?["iad-creative-name"] as? String as Any,
                        "iadKeyword": attribution?["iad-keyword"] as? String as Any,
                        "iadLineitemId": attribution?["iad-lineitem-id"] as? String as Any,
                        "iadLineitemName": attribution?["iad-lineitem-name"] as? String as Any,
                        "iadOrgName": attribution?["iad-org-name"] as? String as Any

                    ]
                    print("////////////////////")
                    print(params)
                }
            }
        })
  }

}

参考文献

iAd frameworkをいれるだけでいいということに気がついた記事
https://groups.google.com/forum/#!topic/firebase-talk/dUxH5VfHG2k

http://www.nikola-breznjak.com/blog/ios/create-native-ios-app-can-read-search-ads-attribution-api-information/

https://stackoverflow.com/questions/50818600/ios-swift-search-ad-api-requestattributiondetails-just-returns-nil

https://forums.developer.apple.com/thread/66161

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftのイニシャライザはややこしい

久しぶりにSwiftのイニシャライザを復習したので、そのときに思ったことや新たに知ったことを、つれづれなるままに書いていきます。つれづれなるままなので「〜思います」系の表現があります。
環境はSwift5.0です。

2つのクラスに継承関係があるとき、サブクラスのinitでは、親のinitを呼ぶ前に自分のところで定義したものを初期化するのはなぜか

class A1 {
    var name: String

    init() {
        print("super")
        name = "unknown"
    }
}

class B1: A1 {
    var age: Int

    override init() {
        print("sub")
        super.init() //エラー Property 'self.age' not initialized at super.init call
        age = 0
    }
}

上の例ではB1のageが初期化されていないとエラーが出ます。初めてこの仕様を見たときは不思議な感じがしました。親を固めて(初期化して)から子を固めるべき、そう感じたからです。
しかし、上のコードの使い方が許されると、次の例ではサブクラスが固まってないのにサブクラスのsomeFunction()が呼び出されてしまうので、こういうのを防ぐ理由もあるかなと気が付きました。

class A1 {
    var name: String

    init() {
        print("super")
        name = "unknown"
        someFunction()
    }

    func someFunction() {
        print("super someFunction")
    }
}

class B1: A1 {
    var age: Int

    override init() {
        print("sub")
        super.init()
        age = 0
    }

    override func someFunction() {
        print("sub someFunction")
        super.someFunction()
    }
}

2つのクラスに継承関係があるとき、親のコンビニエンスinitと同じ引数のinitをサブクラスで指定initとして定義するときの挙動

今回いろいろ調べていてわかったことがあります。誤解を恐れずに書くと
2つのクラスに継承関係があって親が指定initとコンビニエンスinitを持つときに、サブクラスで親と同じ指定initを定義しないで、サブクラスでその指定initが自動生成されると同時に親クラスのコンビニエンスinitも自動生成される条件が整った状況で、親クラスのコンビニエンスinitと同じ引数のinitをサブクラスで指定initとして定義すると、例えばoverrideをつけるように催促するエラーが出ることもなく、コンパイルが通る
です。

class A2 {
    var name: String

    init(name: String) {
        print("super 1")
        self.name = name
    }

    convenience init() {
        print("super 2")
        self.init(name: "unknown")
    }
}

class B2: A2 {

    init() {
        print("sub")
        super.init(name: "unknown")
    }
}

var b2 = B2()
//出力
//sub
//super 1

2つのクラスに継承関係があって親の指定initが2つあるとき、サブクラスでその片方をコンビニエンスinitとして定義するときの挙動

わかったことの2つめは
2つのクラスに継承関係があって親クラスが指定initを2つ持つときに、その片方と同じ引数のinitをサブクラスでコンビニエンスinitとして定義すると、もう片方の指定initは自動生成されて、サブクラスで定義したコンビニエンスinitはoverrideをつけるように催促され、overrideをつけるとそのコンビニエンスinitからself.init()で自動生成された指定initが呼び出せる
です。

class A2 {
    var name: String

    init(name: String) {
        print("super 1")
        self.name = name
    }

    init() {
        print("super 2")
        self.name = "unknown"
    }
}

class B2: A2 {

    convenience override init() {
        print("sub")
        self.init(name: "unknown")
    }
}

var b2 = B2()
//出力
//sub
//super 1

おわりに

*** で詳しく書いてあります。などの情報があれば、よろしくおねがいします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIColorで使っているRGBの値を取得する

概要

UIColor(例えば、UIColor.red)で使われているRGB、alphaの値を取得したい

前提

  • Xcode 10.1
  • Swift 4.2

tl;dr

  • CGColor から取ってくる。
(lldb) po UIColor.yellow.cgColor.alpha
1.0
(lldb) po UIColor.yellow.cgColor.components
▿ Optional<Array<CGFloat>>
  ▿ some : 4 elements
    - 0 : 1.0
    - 1 : 1.0
    - 2 : 0.0
    - 3 : 1.0
print(UIColor.red.cgColor.components![0]) // Red 1.0

extend!

  • UIColorのextensionとして定義して便利に使う
import UIKit

extension UIColor {
    var redColor: CGFloat? {
        return self.cgColor.components?[0]
    }

    var greenColor: CGFloat? {
        return self.cgColor.components?[1]
    }

    var blueColor: CGFloat? {
        return self.cgColor.components?[2]
    }

    var alpha: CGFloat {
        return self.cgColor.alpha
    }
}

print(UIColor.red.redColor) // Optional(1.0)
print(UIColor.red.blueColor) // Optional(0.0)
print(UIColor.red.greenColor) // Optional(0.0)
print(UIColor.red.alpha) // 1.0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで特定の複数の型のみを許容するArray

1つの型を許す配列はもちろん初心者でもすぐ出来ると思います。
では例えば、 IntDouble のみを許す数値の配列を用意したい場合、どうすればいいでしょう。
手段と長短をまとめました。

手段

Enum を利用

enum EnumNumber {
    case int(Int)
    case double(Double)
}

var enumNumbers: [EnumNumber] = [.int(10), .double(2.0)]

Protocol を利用

protocol ProtocolNumber {}
extension Int: ProtocolNumber {}
extension Double: ProtocolNumber {}

var protocolNumbers: [ProtocolNumber] = [Int(10), Double(2.0)]

長所と短所

switch で値を取り出す

Protocol を使用した場合だと、全パターンを書いたとしても default 行を書かなくてはならないので、少し安全ではないコードになってしまうかも。

// Enum
switch enumNumbers.first! {
case .int(let value):       print(value)
case .double(let value):    print(value)
}

// Protocol
switch protocolNumbers.first! {
case let value as Int:      print(value)
case let value as Double:   print(value)
default:                    print("exception")
}

型が分かっている物の値を取得

Protocol だとキャストするだけで値が取れる( optional もしくは forced unwrap ですが )のに対し、
Enum だとおそらく if case 文を書かないといけないのではと思います。となるとインデントが深くなってしまいます。
インデントが深くなるのを回避するために パターン2 のように computed property を生やす手がありますが、もしこうするならば型の数だけ用意しなければなりません。

// Enum パターン1
if case .int(let value) = enumNumbers[0] {
    let enumInt = value
}

// Enum パターン2
extension EnumNumber {
    var integerValue: Int? {
        if case .int(let value) = enumNumbers[0] {
            return value
        }
        return nil
    }
}
let enumInt = enumNumbers[0].integerValue

// Protocol
let protocolInt = enumNumbers[0] as? Int

生成する時

大きな違いは、 Protocol の場合は Int か Double か分かる必要が無い という点です。
Enum の場合は Int か Double か把握した上で Enum を生成 する必要があります。
明らかに Enum のほうが面倒くさくなると思います。

let unknownValue: Any = Int(123)

// Enum パターン1
// このパターンだと、unknownValue が Double の場合、追加されない
if let intValue = unknownValue as? Int {
    enumNumbers.append(.int(intValue))
}

// Enum パターン2
extension EnumNumber {
    init?(_ value: Any) {
        switch value {
        case let value as Int:
            self = .int(value)
        case let value as Double:
            self = .double(value)
        default:
            return nil
        }
    }
}
if let value = EnumNumber(unknownValue) {
    enumNumbers.append(value)
}

// Protocol
if let value = unknownValue as? ProtocolNumber {
    protocolNumbers.append(value)
}

ネストしたい

Swift の設計上 Protocol はネストできません。

class Sample {

    // OK
    enum EnumNumber {}

    // error: protocol 'ProtocolNumber' cannot be nested inside another declaration
    protocol ProtocolNumber {}
}

まとめ

主観で ○ × つけました。

Enum Protocol
switch で値を取り出す ×
型が分かっている物の値を取得 ×
生成する時 ×
ネストしたい ×

まあこんな色々書きましたが、基本的には Protocol を使うべきですね笑

追記: 折衷案

@herara_ofnir3 さんにコメントをいただきました!
上記の表で見ても良いとこ取りできてます。

enum EnumNumber {
    case int(Int)
    case double(Double)
}

protocol EnumNumberConvertible {
    var enumNumber: EnumNumber { get }
}

extension Int : EnumNumberConvertible {
    var enumNumber: EnumNumber { return .int(self) }
}

extension Double : EnumNumberConvertible {
    var enumNumber: EnumNumber { return .double(self) }
}

// 配列を用意
var numbers: [EnumNumberConvertible] = [10, 0.2]

// 1. switch で値取り出し
for number in numbers {
    switch number.enumNumber {
    case .int(let value):    print(value)
    case .double(let value): print(value)
    }
}

// 2. 型が分かっている物の値を取得
let value = numbers[0] as? Int

// 3. 生成する時
let unknownValue: Any = Int(123)
if let value = unknownValue as? EnumNumberConvertible {
    numbers.append(value)
}

参考

環境

  • Swift 5.0

他にもなにかありましたら教えてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AutoLayout+StackViewで自動可変行テーブルビューを実装する

従来、UITableViewCellの内容に応じて行の高さを算出して可変にする実装はかなりめんどくさかった。
AutoLayoutとStackViewで自動的に可変になるように実装してみた。
AutoLayoutはSnapKitを使った。

  • Xcode 10.1
  • Swift 4.2

scroll.gif

サンプルコードは以下に配置
https://github.com/yumemi-ajike/AutoFlexibleRowHeight

UITableViewCellの内部レイアウト

パターン

UITableViewCell のサブクラスとして TableViewCell を追加し、
イメージが縦に配置される VerticalTableViewCell と左横に配置される HorizontalTableViewCell をそれぞれ TableViewCell を共通の親としてサブクラス化した。

イメージの縦配置 イメージの横配置 テキストのみ1行 テキストのみ複数行 テキスト1つのみ
vertical_cell.png horizontal_cell.png vertical_singletext_cell.png vertical_text_cell.png vertical_textonly_cell.png

組み合わせはもっとたくさんあるけどサンプルコードをビルドしたら確認可能なので割愛する。

イメージの縦配置

VerticalTableViewCell クラスで実装している。

ビュー構造

UITableViewCell.contentView に以下のビュー構造で追加していて、
それぞれの UIImageViewUILabelisHidden を変更することでレイアウト内容を変えている。

  • UIStackView
    • UIImageView
    • UILabel
    • UILabel
    • UILabel
  • UIView(下部separator)

表示順

addArrangedSubview した順番に表示される。

TableViewCell.swift
final class VerticalTableViewCell: TableViewCell {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(stackView)
        stackView.addArrangedSubview(thumbnailImageView)
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(dateLabel)
        stackView.addArrangedSubview(descriptionLabel)

        stackView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(insets)
        }
        thumbnailImageView.snp.makeConstraints { (make) in
            make.width.equalTo(stackView.snp.width)
            // 16 : 9 and hidden priority
            make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue)
        }
    }
    ...
}

stackView.axis = .vertical で縦方向に整列するようにし、 stackView.distribution = .equalSpacing で項目間のマージンを均一にしている。
セル内側のマージンは以下で定義して stackViewedgesinset する形でセットしている。

TableViewCell.swift
let insets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)

イメージサイズ

ImageViewの表示サイズの制約は16:9で表示するようにし、
非表示時の対応としてpriorityを .defaultLow にセットしている。
これがないとAutoLayoutの警告が出るので注意。

TableViewCell.swift
        thumbnailImageView.snp.makeConstraints { (make) in
            make.width.equalTo(stackView.snp.width)
            // 16 : 9 and hidden priority
            make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue)
        }

イメージの横配置

HorizontalTableViewCell クラスで実装している。

ビュー構造

UITableViewCell.contentView に以下のビュー構造で追加。

  • UIStackView
    • UIView
      • UIImageView
    • UIStackView
      • UILabel
      • UILabel
      • UILabel
  • UIView(下部separator)

イメージの右横にテキスト行を配置する必要があるため、 UIStackView の入れ子構造とすることで実現している。
また UIImageView がひしゃげてしまったり伸びてしまったりすることを避けるため、 imageBaseView を追加することで回避している。

TableViewCell.swift
final class HorizontalTableViewCell: TableViewCell {
    private let imageBaseView = UIView()
    ...
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        let horizontalStackView = UIStackView()
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        horizontalStackView.axis = .horizontal
        horizontalStackView.alignment = .top
        horizontalStackView.distribution = .fill
        horizontalStackView.spacing = 8
        contentView.addSubview(horizontalStackView)

        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(descriptionLabel)
        stackView.addArrangedSubview(dateLabel)

        horizontalStackView.addArrangedSubview(imageBaseView)
        imageBaseView.addSubview(thumbnailImageView)
        horizontalStackView.addArrangedSubview(stackView)

        horizontalStackView.snp.makeConstraints { (make) in
            make.edges.equalToSuperview().inset(insets)
        }
        ...
    }
}

親のStackViewは horizontalStackView.axis = .horizontal で横方向に整列するようにし、
horizontalStackView.alignment = .top で上揃えになるようにしている。

イメージサイズ

ImageViewの表示サイズは固定で64 x 64になるように制約を設けている。

TableViewCell.swift
        imageBaseView.snp.makeConstraints { (make) in
            make.width.equalTo(64)
            make.height.greaterThanOrEqualTo(imageBaseView.snp.width).priority(UILayoutPriority.defaultLow.rawValue)
        }
        thumbnailImageView.snp.makeConstraints { (make) in
            make.size.equalTo(64)
            make.center.equalToSuperview()
        }

文字や画像のセット

各ビューに文字や画像などの情報を入れ、表示されないビューの isHidden を変更して表示を切り替えている。

TableViewCell.swift
    func configure(with item: Item) {
        thumbnailImageView.kf.setImage(with: item.imageUrl)
        isThumbnailHidden = item.imageUrl == nil
        titleLabel.text = item.title
        titleLabel.isHidden = item.title == nil
        dateLabel.text = item.date
        dateLabel.isHidden = item.date == nil
        descriptionLabel.text = item.description
        descriptionLabel.isHidden = item.description == nil
    }

動作確認

TableViewController.swiftTableViewCell.Item の配列を作ってパターンを網羅するようにしている。

TableViewController.swift
    private let items = [TableViewCell.Item(title: "Short title",
                                            date: nil,
                                            description: nil,
                                            imageUrl: nil),
                         TableViewCell.Item(title: "Short title",
                                            date: "April 7th, 2019",
                                            description: nil,
                                            imageUrl: nil),
                         ...
                         省略
                         ...
                         TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.",
                                            date: "April 7th, 2019",
                                            description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.",
                                            imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image")),
                         TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.",
                                            date: "April 7th, 2019",
                                            description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.",
                                            imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image"))]

感想

かなりシンプルになったし、コード量が激減するので嬉しい!
行高計算のめんどくささからも解放されて喜ばしいけど、AutoLayoutの制約エラーを解決していくのがメイン作業になってこれはこれで慣れが必要。。。
UIStackViewを入れ子構造にしたときにiOS10以前で発生する制約の不整合とかもあるのでその辺の解決にちょっと時間かかった。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift4] バックグラウンドでオーディオ再生する

iOSアプリでオーディオ再生を実装する際に、アプリがバックグラウンドになっても再生し続けるようにする設定手順です。

必要な設定は2つです。

  1. Capabilities設定で、Background Modes - Audio をONにする。
  2. AVAudioSession のカテゴリをPlaybackに設定する。

Capabilities設定からBackground Modesを設定する

プロジェクトファイルを選択肢、「TARGETS」からアプリ名をクリックし、「Capabilities」を選択します。

この中の、 「Background Modes」をON に変更します。
さらに、「Modes」の中から、「Audio. AirPlay. and Picture in Picture」のチェックボックスをONにします。

以下の画面は、これらの設定をした後の画面です。

スクリーンショット 2019-04-09 16.45.14.png

AVAudioSession のカテゴリをPlaybackに設定する

続いて、AVAudioSessionの設定を行います。
AVAudioSessionは、アプリでオーディオをどのように扱うかを宣言するためのオブジェクトです。
例えば、他のアプリで音声再生をしているときの挙動や、バックグラウンドでの音声再生の挙動などを扱うことができます。
AVAudioSession - Apple Developer Document

AppDelegateにて、AVAudioSessionのCategory設定を変更してやります。

AppDelegate.swift
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        /// AVAudioSessionCategory設定
        let session = AVAudioSession.sharedInstance()
        do {
            // CategoryをPlaybackにする
            try session.setCategory(.playback, mode: .default)
        } catch  {
            // 予期しない場合
            fatalError("Category設定失敗")
        }

        // session有効化
        do {
            try session.setActive(true)
        } catch {
            // 予期しない場合
            fatalError("Session有効化失敗")
        }

        return true
    }

以上でオーディオ再生時をアプリがバックグラウンドに移動しても継続できるようになります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

URLSessionを使ってGET通信【Swift】

URLSession

例として郵便番号から住所を取得するとします。

今回利用させて頂くAPI:http://zipcloud.ibsnet.co.jp/doc/api

※プロダクションでは!せずにguardなどで安全に処理してください

var component = URLComponents(string: "http://zipcloud.ibsnet.co.jp/api/search")!
component.queryItems = [URLQueryItem(name: "zipcode", value: "150-0011")]

URLSession.shared.dataTask(with: component.url!) { (data, response, error) in
    let json = String(data: data!, encoding: .utf8)!
    print(json)
}.resume()
console
{
    "message": null,
    "results": [
        {
            "address1": "東京都",
            "address2": "渋谷区",
            "address3": "東",
            "kana1": "トウキョウト",
            "kana2": "シブヤク",
            "kana3": "ヒガシ",
            "prefcode": "13",
            "zipcode": "1500011"
        }
    ],
    "status": 200
}

APIから取得したJSONはCodableという機能を使って型に変換することができます。

URLSessionでもシンプルに実装できたと思います。
他のFrameworkを使う前にURLSessionの使い方を理解しておくのがおすすめです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIレイヤーまでユニットテストを入れているOSSアプリ/フレームワーク/ライブラリポインタ集 

はじめに

UIレイヤーまでユニットテストをがっつりやっているOSSアプリ/フレームワーク/ライブラリです。

商用レベルなiOSオープンソースアプリ集と一部被りますが列挙してみました。

非UIなOSSフレームワーク/ライブラリについてはもはやテストコードがないと相手にすらされないので入れてません。 

Wire iOS

検証方法:スナップショット

TwitterKit

検証方法:プロパティを検証

FireFox

検証方法:無し

Artsy

検証方法:?

Kickstarter

検証方法:MVVM、スナップショット

SpreadsheetView

検証方法:?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む