読者です 読者をやめる 読者になる 読者になる

SH Lab の アプリ開発部屋

リリースしたアプリの告知とかサポートとか、技術的なお話とか、そんな感じでつぶやきます。


ブラよろ 新ブラよろ 海猿 特攻の島 ムショ医

コンビニどこだ? ガススタどこだ? 駐車場どこだ? ぱるインフォ オフサイド Jドリーム

史上最低のレガッタ, イカロスの山, コラソン、マンガアプリ3タイトルをリリースしました!

新作アプリを公開しました

みなさま、いつも当アプリをご利用頂きまして、ありがとうございます。
Susumu Hoshikawaでございます。

今回は、新作マンガアプリ 史上最低のレガッタ, イカロスの山, それに コラソン の3タイトルのご報告です!

塀内夏子さんのマンガ作品を追加!

この3つの作品は、マンガ家塀内夏子さんの作品であり、以前リリースした オフサイド, Jドリーム に続く塀内作品マンガアプリとなります。

3作品とも、塀内さんの作品としては比較的近年の作品であり、記憶に新しい方も多いかもしれませんね。

史上最低のレガッタとは

f:id:hoshi0523:20170126013316p:plain

この作品は、雑誌ヤングマガジンアッパーズに2003年 〜 2004年に連載されていた作品です。

さまざまな事情で少年院に入ることになった9人の少年たちが、少年院を出るためにボート競技「レガッタ」に取り組む、という一風変わったスポーツ青春マンガです。

舞台が舞台なだけに、色んなものを抱えた少年たちが、どのようにして団結してレガッタに取り組んでいくのか?

イカロスの山とは

f:id:hoshi0523:20170126013317p:plain

この作品は、2005年から2007にかけて、講談社発行の週刊モーニングに連載された、本格登山漫画です。

エベレストに人類が初めて登ってから半世紀、新たにヒマラヤの未踏峰が発見されたとのニュースが駆け巡る!

この山への遠征隊には、かつて数々の山々を制覇した名コンビ「三上」と「平岡」が加わる!

果たして、彼らはこの未踏の山を制覇することができるのだろうか!?

コラソンとは

f:id:hoshi0523:20170126013318p:plain

この作品は、2010年から2012年にかけて週刊ヤングマガジンに連載された作品です。

日本代表にスポットを当てたサッカーマンガで、「オフサイド」「Jドリーム」とは一味違った切り口で展開されるサッカーマンガです!

20XX年、日本代表はワールドカップ・アジア最終予選において、得点力不足から5試合を消化した時点で暫定4位に低迷した。日本サッカー連盟はスケジュールの空いていたヘルマン・ヴィースラーに監督就任を要請。

ヘルマンは「全権委任」を条件に要請を受け、ブラジル2部リーグで得点王となっている戌井凌駕を招集するが、彼はかつて暴力事件を起こして日本サッカー界を追放されたいわくつきの男だった…(wikipediaより)

第1巻は完全に無料で!

どの作品も、第1巻は完全無料となっております。

第2巻以降の価格は、それぞれ¥240となっております。

塀内夏子さんの最新作は、マンガonウェブにて!

現在刊行中のWeb雑誌マンガonウェブには、塀内夏子さんの連載も掲載されております。

アプリ内で取り扱っておりますマンガonウェブや、専用アプリからも確認できますので、合わせてこちらもチェックしてみてください!

マンガ on ウェブ

マンガ on ウェブ

  • Susumu Hoshikawa
  • ブック
  • 無料

以上、今後とも当アプリをよろしくお願いいたします!

Appleの Search Ads が始まったので、広告を出稿してみた

f:id:hoshi0523:20161007011024p:plain

Search Ads が始まったよ(アメリカで)

先日、アメリカのAppStoreにて、検索広告サービス Search Ads が始まりました!

アメリカのAppStoreを使っているユーザさんは、検索するたびに広告が表示されるようになったみたいです。

Search Ads ってなに?

Search Ads とは「AppStoreで検索したら、その検索ワードに関連するアプリの広告が表示されるようになる」というものです。

これはAppleが運営する広告サービスで、現在はアメリカのAppStoreのみで導入されています。

広告を出稿したい開発者さんは、Appleにお金を支払って、広告掲載アプリの候補にストックしてもらうような感じです。

ユーザにとっては厄介なサービスにも思えますが、一度の検索で表示される広告は1件のみ広告であることが一目でわかるレイアウトであったりと、一定の配慮はされているようです。

f:id:hoshi0523:20161007011638p:plain

わかりづらさが正義!なネイティブアドとは違うんですよ、と言いたげ。

でもまぁ、日本人にはあんまり関係ない話なんですよね...

お試しクーポンが配られた!

...と思っていたら、日本時間の 9/29 に、Appleさんが「広告サービ始めるから、とりえずお試しで $100.00 相当のクーポン配るでー」という感じで、開発者にばら撒き政策を実施しました。

開発者はみんな、お試しで約¥10,000分相当の広告が無料で打てますよーってことのようです。

アメリカさんしか関係ないサービスなので、追いかけるつもりはなかったのですが...まぁそこまで言うなら使ってみましょうか!

f:id:hoshi0523:20161007011807p:plain
こんな感じのメールが、開発者に届きました。

なお、開発アカウントを持っているユーザ全員に配ったわけではなく、2016/09/10までに iTunes Connect にちゃんと口座を登録している開発者、みたいな縛りがあるようです。

とりあえずアカウントを作る

メールには「Search Ads アカウントを作ったらプロモーションコードあげるでー」って書いてあったので、とりあえずアカウントを作ってみることにしました。

ここの Get started からアカウント登録ができました。

なお、アカウントを作っていた時点では記事にするつもりはなかったため、スクショや細かい手順のメモなどはないのですが...気をつけるポイントとしては

名前や住所は、全角じゃなくて英語で入力しろや!

ってことです。

Appleの開発関連のアカウントなので、全角入力しちゃうような うっかりさん なんていないと思っていたのですが...

知人がうっかり全角で登録して、そのせいでプロモーションコードが届かなくなってしまい、早速 contact us のお世話になっていました。

しかも アカウント名が全角だと、contact us もまともに動かなくなる というバグに遭遇してしまい、訳のわからない状況に陥っていました。

...皆さんはお気をつけください。

発行されたプロモーションコードを有効化する

アカウントを作ったら、無事にプロモーションコードを含んだメールが届きました。英語入力万歳。

f:id:hoshi0523:20161007015305p:plain

メールには「支払い用のクレジットカードを登録したらプロモーションコードを有効にしてやるでー」と書いてありますね...まぁそうですヨネ。おとなしく、クレジットカード情報をAppleに捧げましょう。

クレジットカードと個人情報を入力し、プロモーションコードを入力すると、こんな感じで有効化されるみたいです。

f:id:hoshi0523:20161007021358p:plain

キャンペーンを作る

広告を掲載するために、キャンペーンを作成します。まずは Search Ads の管理画面のトップにて Create Campaign を選択します。

f:id:hoshi0523:20161007024332p:plain

掲載するアプリを選択します。

f:id:hoshi0523:20161007024405p:plain 唯一、海外版のコンテンツが存在する「ブラックジャックによろしく」のアプリを選択しておきました(他に選択肢ないですし...)。

アプリを選択すると、いろいろと細かい入力欄が表示されるみたいですね。

f:id:hoshi0523:20161007024730p:plain

キャンペーン

広告出稿の大枠で、対象になるアプリとキャンペーンの名前、全体の予算を設定する。オプションで、1日ごとの予算も設定できるみたいでした。

  • App Name
    • キャンペーン対象のアプリ
  • Campaign Name
    • キャンペーンに対して名前をつける
  • Budget
    • キャンペーン全体のご予算
  • Maximum Daily Spend
    • キャンペーンの1日のご予算
    • オプションっぽいので、設定しなくても良さそう
  • Campaign Negative Keywords
    • キャンペーンの対象から外すキーワードを設定
    • このキーワードには反応させない、という設定ができる

グループ

キャンペーンには、複数のグループを紐づけられます。クリック単価や掲載スケジュールは、グループの単位で設定します。

  • Ad Group Name
    • グループに対して名前をつける
  • Storefronts
    • 対象地域を選ぶっぽいのですが、今はUSしか選択できない
  • Devices
  • Ad Scheduling
    • グループの実施期間を設定可能
  • Default Max CPT Bid
    • 1クリックに対していくらまで支払うか?を設定
    • ここで定義した金額で入札する、という扱いになるようです
    • 大きい方が掲載されやすい、とかでしょうかね

Search Match

キャンペーンごとに設定する情報。iTunes Connect に登録しているアプリのメタデータをもとに、いい感じで広告を掲載するスイッチ?そんなこと言われたら、ONにするしかないじゃない。

  • Search Match
    • 検索マッチ機能をON/OFFできる
    • アプリのメタデータをもとに、最適化して掲載してくれるらしい?
    • とりあえずONにしておけば良さそうな雰囲気

キーワードの設定

グループには、複数のキーワードが設定可能です。クリック単価は、紐付いているグループに依存します。

  • Recommended Keywords
    • アプリのメタデータで設定しているキーワードから選択
  • Ad Group Keywords
    • 直接入力して設定する
    • メタデータにいい感じのキーワードがないら、直接入力しよう
  • Ad Group Negative Keywords
    • 逆に、反応させないキーワードをここで設定する

ユーザの細かいターゲッティング

キャンペーンごとに設定する詳細設定です。広告を表示するユーザを、細かくターゲッティングできます。

  • Customer Types
    • 自分の他のアプリをもっているかどうか?などで絞り込める
  • Demographics
    • 性別
    • 年齢
  • Locations
    • 位置情報で絞る
    • 特定の地域で利用可能なサービスアプリなどであれば、これで絞るといい
    • まぁアメリカ内ということには変わりないんですけどね

キャンペーンのイメージ図

アプリ、キャンペーン、グループなど、登場人物が多いが、全体をまとめるとこんなイメージ。

f:id:hoshi0523:20161007035544p:plain

ちゃんと表示されるのかな

管理画面では、作成したキャンペーンやグループが確認できるようになりました。

f:id:hoshi0523:20161007035938p:plain

f:id:hoshi0523:20161007035951p:plain

f:id:hoshi0523:20161007035958p:plain

が、設定したキーワードでは、他のアプリの広告ばかりが表示されてしまい、ウチのアプリは全然表示してもらえないですね...ちゃんと表示されるんでしょうか (汗)

データを見ながらキーワードや単価を調整する

自分としては実験の域は出ませんが、もう少し様子を見ながら、掲載される瞬間を楽しみにしたいですね。

ですが、ちゃんと予算を組んで広告の掲載を行う場合は、管理画面に表示されるデータを見ながら、キーワードや単価を調整して、効果の高いやり方を探っていくことになりそうですね。

レポートもかなり細かく確認できるようですので、世の中的に情報が出回ってきたら、もう少し突っ込んで調査してみようと思います。

マンガ「オフサイド」と「Jドリーム」のアプリをリリースしました!

みなさま、いつも当アプリをご利用頂きまして、ありがとうございます。
Susumu Hoshikawaでございます。

今回は、新作マンガアプリ オフサイドJドリーム のご報告です!



塀内夏子さんのサッカーマンガ!

この2つの作品は、マンガ家塀内夏子さんが手がけたサッカーマンガです。

ともに少年マガジンを支え続けた作品で、オフサイドは1987年から1992年にかけて、Jドリームは1993年から1999年にかけて連載されていました。

30〜40代くらいの男性にとっては、懐かしい作品なのかもしれません。

オフサイドとは

オフサイドは、高校サッカーを舞台にしたサッカーマンガで、2001年にはアニメ化もされた作品です。

イラストからも分かる通り、主人公の熊谷五郎の連載開始時点でのポジションはキーパーであり、サッカーマンガの主人公としては珍しいイメージですね。

f:id:hoshi0523:20160813155746j:plain

Jドリームとは

Jドリームは、オフサイドの連載が終了した翌年から連載が開始された、同じくサッカーマンガです。ですが、高校サッカーではなく、プロサッカーリーグJリーグを舞台としています。

連載が開始された1993年とは、Jリーグが発足した年であり、日本中がサッカーで熱狂していた年です!

このJドリームも、Jリーグの開幕前後が舞台となっており、当時の日本のサッカー熱が見事に描かれております。

さらには、アメリカワールドカップやフランスワールドカップへと舞台が移っていき…是非とも続きはアプリでご賞味ください!

f:id:hoshi0523:20160813155747j:plain

第1巻は完全に無料で!

どちらも10巻を超える作品ですが、第1巻は完全無料となっております。

第2巻以降の価格は、それぞれ¥360となっております。

塀内夏子さんの最新作は、マンガonウェブにて!

現在刊行中のWeb雑誌マンガonウェブには、塀内夏子さんの連載も掲載されております。

アプリ内で取り扱っておりますマンガonウェブや、専用アプリからも確認できますので、合わせてこちらもチェックしてみてください!

マンガ on ウェブ

マンガ on ウェブ

  • Susumu Hoshikawa
  • ブック
  • 無料

以上、今後とも当アプリをよろしくお願いいたします!

tvOS向けアプリとフレンチトーストの作り方を解説してみる

AppleTVのアプリ開発していたらお腹が空いた

f:id:hoshi0523:20160508120855j:plain

自宅でtvOS向けアプリの開発していたら、無性にフレンチトーストが食べたくなったので、ノリで作ってみました。これがまずまずの美味しさだったので、アプリ作りフレンチトースト作りについて、備忘録を兼ねて解説していきたいと思います。フレンチトースト - Wikipedia

ちなみにアプリは、テレビ向けっぽく動画のアプリがよかったので、Youtube動画を再生できるアプリを作ってみます。

用意したものはこちら

f:id:hoshi0523:20160508120931j:plain

今回用意したのはこちらです(※1人分)。

  • パン → 1枚(厚手のもの)
  • バター → 少々
  • たまご → 1個
  • 牛乳 → 60cc
  • 砂糖 → スティックシュガー1本分
  • 開発機Macbook Pro
  • AppleTV → 第4世代
  • Siri RemoteiPhoneでも代用可能
  • USBケーブル → Type-C

バニラエッセンスがあると尚良いらしいですが、最寄りのスーパーに売ってなかったので今回は見送りました。

アプリの概要

今回作成したアプリはこちらに置いてあります。

github.com

作ろうとしているアプリの概要は、こんな感じです。

  1. あらかじめ、Youtubeのプレイリストを作成しておく
  2. アプリ起動時、Youtube API を使ってプレイリストを取得する
  3. 取得したプレイリスト内のサムネイルを、グリッド状に表示する
  4. 任意のサムネイルを選択すると、プレイヤー画面に遷移して再生開始

まずはプロジェクト作りから

複雑なアプリではないので、アプリのテンプレはSingle View Applicationを選択し、アプリ名をYoutubeViewerとしておきます。

f:id:hoshi0523:20160507234855p:plain

cocoapodsでライブラリを入れよう

cocoapodsを使って、必要なライブラリを入れておきます。Podfileはこんな感じで設定してみました。

platform :tvos, '9.0'
use_frameworks!

target 'YoutubeViewer' do
  pod 'HCYoutubeParser'
  pod 'Unbox'
  pod 'AlamofireImage'
end
  • HCYoutubeParser
    • Youtubeのコンテンツに対して、AppleTVでも再生可能な形式のURLを生成してくれるライブラリ。
  • Unbox
  • AlamofireImage
    • 画像の非同期取得ライブラリ
    • Alamofireを使うので、せっかくなのでこれも使おう


Unboxの使い方については、ギャップロさんを参考にさせてもらっていますよ。

www.gaprot.jp

また、フレンチトーストについては、こちらのサイトさんを参考にさせてもらっていますよ。

food.kihon.jp

卵液を作ろう

f:id:hoshi0523:20160508121007j:plain:w600

パンに染み込ませるための卵液の作り方です。とは言っても、卵をよく溶いて、牛乳・砂糖(とバニラエッセンス)を加えて、しっかりと混ぜるだけです。

パンを浸そう

f:id:hoshi0523:20160508121029j:plain:w600

出来上がった卵液に、パンを浸します。

f:id:hoshi0523:20160508121035j:plain:w600

箸でつまむと、卵液をよく吸います。卵液が全て吸われてなくなるまで、両面をしっかりと浸しましょう。このまま放置するのも良いと思います。
それでも卵液が余るようなら、第2・第3のパンを投入しましょう。

Youtubeでプレイリストを作っておく

www.youtube.com

YoutubeViewer - YouTube

今回のアプリ内に並べるYoutubeのコンテンツは、プレイリストを作って用意することにします。適当なプレイリストを作成して、そのプレイリストのIDを控えておきましょう。

ちなみに自分は、今回のアプリ用にこんな感じのプレイリストを作ってみました。昔よく聞いていたバンドや照井春佳さん出演コンテンツ、なにかと所縁のあるコンテンツなどを並べてみました。特に先頭の動画はとてもカッコよろしいです。

このプレイリストのIDは PLrFwetrdOQ9ixp8Kj0mqBLKQAG9DTViJA だそうな。こちらはURLを見るとわかります。

YoutubeAPIを利用してプレイリストを取得する

まずはアプリからYoutubeAPIを実行して、プレイリストのデータを取得してみましょう。YoutubeAPIの仕様については、この辺りの公式ドキュメントを参考にするとよさそうです。

なお、YoutubeAPIを利用するには、Google API Key が必要です。API Keyは、各々で取得してください。以下のドキュメントから API キー → ブラウザ キー あたりを参考に取得すれば、とりあえず動作可能です。

APIの実行とJSONのパース(ViewController.swift

API通信部分は、Alamofireを使って、こんな感じでメソッドとして定義しています。

// API実行用のメソッド.
private func requestPlaylist(completeHander: Response<AnyObject, NSError> -> Void) {
  // プレイリストを取得するためのURL.
  let url = "https://www.googleapis.com/youtube/v3/playlistItems"
  // リクエストに利用するパラメータ.
  var parameters = [String: String]()
  parameters["part"] = "snippet,contentDetails"
  parameters["maxResults"] = "50"
  parameters["playlistId"] = "PLrFwetrdOQ9ixp8Kj0mqBLKQAG9DTViJA"
  parameters["key"] = "自分の API Key を使ってね"
  // リクエスト実施.
  Alamofire
    .request(.GET, url, parameters: parameters)
    .responseJSON(completionHandler: completeHander)
}

viewDidLoad()あたりで、上記のメソッドを呼び出しています。

// APIを実行する.
self.requestPlaylist { [weak self] response in
  // レスポンスをパースする.
  if let data = response.data {
    do {
      self?.playlistItems = try Unbox(data) as PlaylistItems
    } catch let error {
      print("error = \(error)")
    }
  }
}

JSONデータのパースのためのDTO(YoutubePlaylistModel.swift

受け取ったJSONデータの構造に対応するDTOを、ひたすら定義していきます。今回は、利用する項目が限られているので、不要な項目に対応する構造体やプロパティは、省略しております。

struct PlaylistItems {
  var items: [VideoItem]
}
struct VideoItem {
  let contentDetails: ContentDetails
  let snippet: Snippet
}
struct ContentDetails {
  let videoId: String
}
struct Snippet {
  let thumbnails: Thumbnails
}
struct Thumbnails {
  let mediumThumb: Thumbnail
}
struct Thumbnail {
  let url: NSURL
}

DTOを、Unboxableプロトコルに準拠させます。

extension PlaylistItems: Unboxable {
  init(unboxer: Unboxer) {
    self.items = unboxer.unbox("items")
  }
}
extension VideoItem: Unboxable {
  init(unboxer: Unboxer) {
    self.contentDetails = unboxer.unbox("contentDetails")
    self.snippet = unboxer.unbox("snippet")
  }
}
extension ContentDetails: Unboxable {
  init(unboxer: Unboxer) {
    self.videoId = unboxer.unbox("videoId")
  }
}
extension Snippet: Unboxable {
  init(unboxer: Unboxer) {
    self.thumbnails = unboxer.unbox("thumbnails")
  }
}
extension Thumbnails: Unboxable {
  init(unboxer: Unboxer) {
    self.mediumThumb = unboxer.unbox("medium")
  }
}
extension Thumbnail: Unboxable {
  init(unboxer: Unboxer) {
    self.url = unboxer.unbox("url")
  }
}

CollectionViewで、画面上にサムネイルを表示する

データの取得が成功したら、CollectionViewを使って画面にサムネイルを並べていきます。

f:id:hoshi0523:20160508130123p:plain

Storyboardでは、UICollectionViewを画面いっぱいに広げて配置しています。CellのクラスはYoutubeCellに変更し、サイズいっぱいにUIImageViewを配置しています。dataSource, delegate, IBOutlet の接続なども忘れずに行いましょう。

collectionViewの準備と表示処理(ViewController.swift

通信の取得後に、collectionViewを更新する処理を追加します。

// APIを実行する.
self.requestPlaylist { [weak self] response in
  // 省略.
  // リクエスト取得後にcollectionViewをリロード.
  self?.collectionView.reloadData()
}

UICollectionView関連のプロトコルを、いい感じで実装しましょう。

extension ViewController: UICollectionViewDataSource {
  func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return self.playlistItems?.items.count ?? 0
  }
  func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(YoutubeCell.CellIdentifier, forIndexPath: indexPath) as! YoutubeCell
    cell.videoItem = self.playlistItems?.items[indexPath.item]
    return cell
  }
}
extension ViewController: UICollectionViewDelegate {
  func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    // TODO: 保留.
  }
}
extension ViewController: UICollectionViewDelegateFlowLayout {
  // セルの大きさ.
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    return YoutubeCell.CellSize
  }
  // collectionView全体のInsets.
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 60, left: 90, bottom: 60, right: 90)
  }
  // セル間のスペースの最小値(Y軸方向).
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
    return 60 // 適当に.
  }
  // セル間のスペースの最小値(X軸方向).
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAtIndex section: Int) -> CGFloat {
    return 60 // 適当に.
  }
}

サムネイルの表示処理など(YoutubeCell.swift

セルのクラスでは、画像の非同期取得処理と、フォーカス時の拡大処理などを実装しておきました。

import UIKit
import AlamofireImage
class YoutubeCell: UICollectionViewCell {
  static let CellIdentifier = "YoutubeCell"
  static let CellSize = CGSize(width: 320.0, height: 180.0)
  @IBOutlet weak var thumbnailImageView: UIImageView!
  var videoItem: VideoItem? {
    didSet {
      // URLが存在する場合は、画像を取得して当て込む.
      if let url = self.videoItem?.snippet.thumbnails.mediumThumb.url {
        self.thumbnailImageView.af_setImageWithURL(url, imageTransition: .CrossDissolve(0.5))
      }
    }
  }
  override func awakeFromNib() {
    super.awakeFromNib()
    // フォーカスが当たった時のアレ.
    self.thumbnailImageView.clipsToBounds = false
    self.thumbnailImageView.adjustsImageWhenAncestorFocused = true
  }
}

アプリを起動してみよう

ここまでくれば、画面上にプレイリストのサムネイルが表示されます。なかなかいい感じですね。「お?このPV知ってっぞ」と思った方は、なかなかのおっさんです。

f:id:hoshi0523:20160508131747p:plain:w600
f:id:hoshi0523:20160508133435g:plain:w600

パンを焼こう

ここまでくると、パンが卵液をしっかり吸い込んでいると思いますので、さっそくパンを焼いていきましょう。

f:id:hoshi0523:20160508135112j:plain:w600

フライパンにバターを入れ、中火で溶かしていきます。溶けたら弱火にしましょう。

f:id:hoshi0523:20160508135348j:plain:w600

パンを投入します。弱火のまま、じっくりと焼いていきましょう。

f:id:hoshi0523:20160508135446j:plain:w600

両面とも焼いていきます。こんがりきつね色の焦げ目がついたら、フレンチトーストは完成です!腹減ったね!

画面遷移を実装してみよう

プレイリストが取得できて、コンテンツが表示されるようになりました。あとは動画が再生できれば目標達成です。まずは「任意のコンテンツを選択したらその動画の情報を次の画面に渡す」という処理を実装してみます。

遷移先の画面を作成(PlayerViewController.swift

プレイヤー用のViewControllerとしてPlayerViewControllerクラスを作成しておきます。AVKitをimportし、AVPlayerViewControllerを継承します。また、前の画面からコンテンツ情報を受け取る必要があるので、インスタンス変数としてVideoItemを定義しておきましょう。

import UIKit
import AVKit

class PlayerViewController: AVPlayerViewController {
  var videoItem: VideoItem?
}

storyboardで画面とsegueを追加

storyboard上では、AVPlayerViewControllerを配置して、トップ画面からsegueをつないでおきます。segueのidentifierは、適当に設定しておきましょう。配置したAVPlayerViewControllerのクラスは、先ほど作成したPlayerViewControllerに変更しておきましょう。

f:id:hoshi0523:20160508142252p:plain

コンテンツが選択された際の挙動(ViewController.swift

トップ画面で任意のコンテンツを選択された際は、選択されたindexPathをもとにVideoItemを取得し、インスタンス変数として保持します。あとは、先ほど設定したsegueを呼び出して画面遷移を行います。

extension ViewController: UICollectionViewDelegate {
  func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
    // 選択されたコンテンツを保持してプレイヤー画面へ.
    if let videoItem = self.playlistItems?.items[indexPath.item] {
      self.selectedVideoItem = videoItem
      self.performSegueWithIdentifier(ViewController.SEGUE_PLAY_MOVIE, sender: nil)
    }
  }
}

遷移のタイミングで呼ばれるメソッドで、PlayerViewControllerに対してVideoItemを渡します。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  // プレイヤー画面への遷移の場合.
  if segue.identifier == ViewController.SEGUE_PLAY_MOVIE {
    // 次の画面に、選択されたVideoItemを渡す.
    let playerViewController = segue.destinationViewController as! PlayerViewController
    playerViewController.videoItem = self.selectedVideoItem
  }
}

この辺りのロジックはいくらでもやりようがあるので、お好みで実装してくださいな。

動画の再生を行う

VideoItemを渡して再生画面まできたら、あとはURLをAVPlayerに渡せば再生開始となります。プレイリストに含まれるvideoIdから、AppleTVで再生可能なURLを生成するために、HCYoutubeParserというライブラリを利用します。iPhoneであれば、他にもWebViewを利用した再生も可能のなのですが、tvOSの場合はAVKitを使う必要があるため、この方法をで再生します。

import UIKit
import AVKit
import HCYoutubeParser
class PlayerViewController: AVPlayerViewController {
  var videoItem: VideoItem?
  override func viewDidLoad() {
    super.viewDidLoad()
    // videoIdを取り出す.
    guard let videoId = self.videoItem?.contentDetails.videoId else { return }
    // videoIdを元にコンテンツ情報を取得する.
    guard let dic = HCYoutubeParser.h264videosWithYoutubeID(videoId) else { return }
    // コンテンツのURLを取得して再生開始.
    if let urlString = dic["medium"] as? String, let url = NSURL(string: urlString) {
      self.player = AVPlayer(URL: url)
      self.player?.play()
    }
  }
}

HCYoutubeParserのメソッドで、videoIdからコンテンツURLを取得しています。取得したURLをAVPlayerに渡して再生を開始します。

f:id:hoshi0523:20160508153516p:plain:w600
f:id:hoshi0523:20160508153818p:plain:w600

ちゃんと再生できました!ちょっとわかりづらいですが、シークなども何もせずに利用可能になります。

tvOS向けアプリに関しては、動画アプリとはとても相性がいいと思うので、こんな感じで動画ビューワアプリを作ってみるのもいいかもしれないですね。

再生できない動画もあるみたい...?

作成したプレイリストのうち、再生できない動画が一定数ありました。試行回数もそれほどではないので定かではありませんが、どうやら古めのコンテンツについては再生ができないみたいでした。

アプリもフレンチトーストも完成した!

f:id:hoshi0523:20160508154750j:plain

というわけで、無事にアプリもフレンチトーストも完成しました。どちらもそれほど苦なく作成することが可能なので、よかったらお試しくださいませ!

swiftへのツッコミ、コード埋め込みやシンタックスハイライトへのツッコミ、調理へのツッコミ、撮影技術へのツッコミ等、お待ちしておりますm( _ _ )m

マンガ on ウェブ 第4号を配信!

あけましておめでとうございます!

みなさま、あけましておめでとうございます。
Susumu Hoshikawaでございます。
2016年もなにとぞよろしくおねがいいたしますm( _ _ )m

マンガ on ウェブ 第4号が配信開始♪

本日2016年1月1日から、Web雑誌 マンガ on ウェブ の 第4号 が配信開始となります!

f:id:hoshi0523:20150918203751p:plain

第4号は2冊で同時配信!

今号は、あまりにも特大ボリュームのため、side-A side-B の2冊同時配信となっております!

f:id:hoshi0523:20151231160045j:plainf:id:hoshi0523:20151231160053j:plain

お取り扱いはこれらのアプリにて!

第4号は、これらのアプリにて取り扱っております!

マンガ on ウェブ

マンガ on ウェブ

マンガ on ウェブ

  • Susumu Hoshikawa
  • ブック
  • 無料

各種マンガアプリ

無料で1巻!海猿 -UMIZARU-

無料で1巻!海猿 -UMIZARU-

  • Susumu Hoshikawa
  • ブック
  • 無料
無料で1巻!特攻の島

無料で1巻!特攻の島

  • Susumu Hoshikawa
  • ブック
  • 無料

Susumu Hoshikawaの App を App Store で

アプリでは、¥360にてお取り扱いしておりますが、各号ともに 無料お試し版 がございます!まずは無料にて、お試しくださいませ!

f:id:hoshi0523:20150918211845p:plain

以上、最後になりますが、今年もよろしくお願いします!

特攻の島 第8巻が配信開始!

みなさま、いつも当アプリをご利用頂きまして、ありがとうございます。Susumu Hoshikawaでございます。

今回は、拙作アプリ無料で1巻!特攻の島に関するご報告です。

無料で1巻!特攻の島

無料で1巻!特攻の島

  • Susumu Hoshikawa
  • ブック
  • 無料

最新第8巻、アプリで登場!

佐藤秀峰さんが手がける漫画特攻の島の最新巻第8巻が、iPhoneiPadアプリ無料で1巻!特攻の島にて配信を開始いたしました!

つい先日(11/16)に発売された書籍版に続いて、アプリでも早速取り扱いが開始いたしましたよ!

特攻の島は、特攻兵機「回天」の搭乗員の姿を描いた物語で、当然舞台は太平洋戦争真っ只中。いよいよ物語もクライマックス、最後の作戦に挑む渡辺の運命は!?是非ともご覧下さい!

表紙はこんな感じ!

最新巻の表紙はこんな感じになっています。

これまで全巻、渡辺が表紙を飾っておりますが...前巻までの鬼気迫る表情もなく、何かを悟ったかのような表情になっていますね。物語がどのような展開を迎えるのでしょうか...

f:id:hoshi0523:20151122182516p:plain


価格は¥240!

第2〜7巻と同様、価格は¥240となっております。書籍版と比べると、なんと半額以下です!

場所もとらずに安くあがる、いつでもどこでも読めるiPhone/iPadアプリ版の特攻の島、どうぞお楽しみください!

無料で1巻!特攻の島

無料で1巻!特攻の島

  • Susumu Hoshikawa
  • ブック
  • 無料

以上、今後とも当アプリをよろしくお願いいたします!

Storyboard Reference がいい感じ

勉強会で持ち帰ったトピックが気になった

先日参加した iOS 9 の勉強会にて、岸川克己さん@Realm が話された Storyboard Reference の話が非常に興味深かったので、自分なりに追加調査してみました。

dev.classmethod.jp

まずはstoryboardの欠点とか

storyboardは、xibでの開発と比べて、画面の遷移が視覚的に分かりやすいのと、segueを利用できるという利点があります。ですが、ファイルが1つなので、チーム開発を行うと競合しやすく、分業しづらいという欠点があります。

そのため、segueを利用できるという利点を破棄してでも画面ごと機能ごとにstoryboardを分割する、という手法が選択されがちだと思います。

分割した別のstoryboardを利用するためには、プログラム側でいろいろやる手間が発生してしまいます。遷移にsegueは利用できず、視覚的にも伝わりづらくなってしまうので、出来れば避けたいですね。

ちなみに、storyboardについてあまりご存知ない方は、この辺りを参考にしてみるといいかもしれません。


ぼっち開発でも困る時は困る

自分のように1人で作業する分には、分業を考える必要はあまりありません。ですが、1つのstoryboardファイルが膨れ上がってしまうのは、やはりあまり嬉しい状況ではないですね。

ちなみに、自分がリリースしている某アプリのstoryboardはこんな感じで、晒すのも恥ずかしいくらい管理が行き届いておりません...

f:id:hoshi0523:20151003190542p:plain

ですが、Xcode 7 からは、この欠点を補うべく、Storyboard Reference という機能が追加されました。


Storyboard Reference とは?

Storyboard Reference は、分割したstoryboardファイル同士を参照し、segueでつなぐことができる機能です。

つまり先ほど挙げた、ファイル分割時の欠点が補われるということですね!

  • 別のstoryboardへの参照もsegueで記述できる
  • 遷移のためのプログラム記述は不要


とりあえず使ってみた

まずは今まで通りに

まずはこんな感じで、画面Aのボタンをタップすると画面BにPUSH遷移を行う、というstoryboardを作ってみます。これまで通りで、特筆すべき点は無いですね。

f:id:hoshi0523:20151003213830p:plain


別のstoryboardファイルを作る

次に、別のstoryboardファイルを生成します(Green.storyboard)。そこに、ViewControllerを1つ配置して、コレを Initial ViewController とします。この画面は画面Cとします。

f:id:hoshi0523:20151003214306p:plain


Storyboard Reference を使う!

画面B上にボタンを配置し、このボタンをタップしたら画面Cに遷移するようにしましょう。

Main.storyboardに、右下のオブジェクト一覧から、storyboard reference をドラッグして配置します(①)。

この Storyboard Reference が Green.storyboard を参照するように設定します(②)。

画面B上のボタンから、この Storyboard Reference に対してPUSH遷移のsegueを設定します(③)。

f:id:hoshi0523:20151003220411p:plain

これで動かしてみれば、画面A画面B画面C の順番で遷移していくと思います。

f:id:hoshi0523:20151003222310g:plain


既存のファイルを分割する場合は?

自分のアプリのように、すでに膨らんでいるstoryboardを分割するのも、編集メニューから簡単に行うことができます。

今回は、Main.storyboard画面B を、別のstoryboardファイルとして分離してみます。


storyboardを分割するよ

分離したいオブジェクトを選択した状態で(①)、Editor → Refactor to Storyborad ... を選択します(②)。

f:id:hoshi0523:20151003223527p:plain


新しくstoryboardが生成された

そうすると、新たにstoryboardが生成されます!(ファイル名は Blue.storyboard としました。)

中には 画面B の ViewController があって、そこから Green.storybard に遷移できるようになっているみたいです。想定通りですね。

f:id:hoshi0523:20151003224155p:plain


元になったファイルも変更されてますね

分割元となった Main.storyboard も、正しく変更されているようです。

見てみてると、画面A の ViewController のみになっており、画面B の代わりにStoryboard Reference が配置されています。これは、先ほど生成された Blue.storyboard を参照しているようです。

f:id:hoshi0523:20151003224803p:plain

この方法を使えば、すでに膨らんでしまったstoryboardファイルを抱えて悩んでいる方も、簡単にファイルを分割できそうですね〜。


iOS 9 のみで使える機能なの?

ちなみに、この Storyboard Reference は、どのバージョンのiOSをサポートしているのでしょうか...?

今回作成したアプリは、Deployment Target を 9.0 にしていましたが、これを 8.0, 7.0 に変更してビルドしてみます。

f:id:hoshi0523:20151004004412p:plain

f:id:hoshi0523:20151004003645p:plain

どうやら、Storyboard Reference は、iOS 8.0 以降の機能っぽいですね。


Relationship Segue も行けちゃう

Navigation ControllerTab Bar Controller のようなコンテナ系の ViewController には、関係性を定義する Relationship Segue というsegueを設定します。これらにも、Storyboard Reference を利用できます。


ベースにあるNavigation Controller

ベースには Navigation Controller を配置しています。Navigation Controller の場合は、Relationship Segue を利用して Root ViewController を設定する必要があります。

今回は Storyboard Reference を利用しているので、Root ViewController には、別のstoryboardファイルに定義された ViewController を設定しています。

f:id:hoshi0523:20151004014317p:plain


Tab Bar Controller が中にいる

Navigation Controller の Root ViewController には、Tab Bar Controller を設定しています。当然、この Tab Bar Controller は、Initial ViewController として設定されています。

Tab Bar Controller にも、Relationship Segue を利用して、各Tabの ViewControllers を設定する必要があります。ここも Storyboard Reference を利用し、別のstoryboardファイルに定義された ViewController複数設定しています。

f:id:hoshi0523:20151004014649p:plain


各タブごとの ViewController

各タブに利用している ViewController は、一つのstoryboardファイルにまとめて定義しています。

f:id:hoshi0523:20151004015202p:plain

...あれ?Initial ViewController が定義されていなくね?ちゃんと呼ばれるの??と、思う方もいるかもしれません。

Storyboard Reference を利用する場合は、ここまで説明し的してきた「利用するstoryboardファイルを指定して、Initial ViewController に設定されたViewControllerを呼び出す方法」「利用するstoryboardファイルと、利用するViewControllerの Storyboard ID を指定する方法」の2通りあります。


Storyboard ID を使う場合

1つのstoryboardに複数のViewControllerを定義している場合は、後者の方法を採用することで、利用するViewControllerを使い分けることができます。

その場合、各ViewControllerには、それぞれ一意となる Storyboard ID を定義する必要があります。

f:id:hoshi0523:20151004021145p:plain


そして、それらのViewControllerを利用するStoryboard Referenceには、storyboardファイル名とReference ID(Storyboard ID)を設定します。

f:id:hoshi0523:20151004021200p:plain


これを iOS 8 で利用しようとすると...

先ほど調査したように、Storyboard Reference は iOS 8 以降で使えるんだよな〜と思っていると...怒られました

f:id:hoshi0523:20151004022441p:plain

どうやら、Relationship Segue に対して Storyboard Reference を利用する場合は、Deployment Target が 9.0 以降である必要があるみたいですね。

ということで、iOS 8 系をターゲットとするアプリの場合は、relationship segue に対して Storyboard Reference を利用しないようにしましょう!


分割しちゃうと unwind segue って使えないの...?

個人的には、モーダルを閉じる際に unwind segue を使うのですが、ファイルを分割しちゃうと使えなくなっちゃうでしょうか...?ちょっと試したんだけど、やり方がわからなかったです。

設定画面はモーダル表示で、Storyboard Reference を使うぞ! みたいなことって、割と普通にありそうなのですが、こういう場合は unwind segue は諦めろってことなのかな...?

この件に関しては、もう少し調査しないといけないですね(汗)


早く使いたい機能だよ。iOS 7 とか切りましょうよ。

ということで、Storyboard Referenceは、とても魅力的で開発にも良い効果を与えそうな新機能でした。チーム開発を行っている現場では、特に重宝されるんじゃないですかね。

ただ、この機能を導入するには、最低でも iOS 7 のサポートを終了する必要があります。サービスへの影響は小さくないかもしれませんので、サービスの規模によっては、慎重にならざるをえないかもしれませんね...

ちなみに、自分の某アプリのiOSバージョン分布はこんな感じでした。

f:id:hoshi0523:20151004023547p:plain

もう iOS 7 切ってもよくないっすか?