酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Xamarin.iOSでStoreKitを使って広告非表示やアイテム課金などの商品(プロダクト)の一覧を取得する

Xamarin.iOSでStoreKitを使って広告非表示やアイテム課金などの商品の一覧を取得してみましょう。

StoreKit上では前述の商品のことを「プロダクト」と呼びます。StoreKitでの基本的な処理の流れとしては、SKProductXXXXクラスでプロダクトの情報を取得して、SKPaymentXXXクラスでプロダクトの購入をおこないます。

ここでは仮にjp.ch3cooh.exampleというIDのプロダクトの情報を取得したいと思います。

// プロダクトの一覧を羅列する
var products = new NSSet(new []{
    "jp.ch3cooh.example",
});
ProductRequest = new StoreKit.SKProductsRequest(products);
ProductRequest.RequestFailed +=  (sender, e) => {
    Console.WriteLine("プロダクトの取得に失敗しました");
};
ProductRequest.ReceivedResponse += (sender, e) => {
    Console.WriteLine("プロダクトの取得に成功しました");

    // レスポンスのあったプロダクトの一覧の中から
    // 任意のIDのプロダクト情報を取得する
    var product = e.Response.Products
        .SingleOrDefault(res => res.ProductIdentifier == "jp.ch3cooh.example");
 
    // プロダクトIDを取得する
    var pid = product.ProductIdentifier;

    // ローカライズされたプロダクトの名称を取得する
    var title = product.LocalizedTitle;

    // ローカライズされたプロダクトの値段を取得する
    // (拡張メソッドです:詳しくは後述にて)
    var price = product.GetLocalizedPrice();

    // プロダクトの説明
    var description = product.LocalizedDescription;
};

// プロダクトの詳細情報(日本語や英語での名前や料金など)を要求する
ProductRequest.Start();

上記のサンプルコードででてきた拡張メソッドのGetLocalizedPriceメソッドについて説明します。

StoreKitでのプログラミングガイドでも推奨されていますが、プロダクトの価格を静的なリソースとして持つべきではありません。つい先日も日本のAppStoreでのアプリ値段の値上げが発表されましたね。このように、Appleの任意のタイミングで価格の変更ができるようになっているからです。

プロダクトの販売時点の値段を正しく表示するためには、ユーザーからすると多少待ち時間が発生してしまうのですが毎回プロダクト情報を取得して値段を組み立てる必要があります。「組み立てる」という言葉を使ったのはプロダクトの価格が「1,000円」 という形でくるのではなく、値段(Priceプロパティ)とその値段が示すロケール(PriceLocaleプロパティ)を元にNSNumberFormatterクラスを使うからです。……といった定型的な処理をする必要があるので拡張メソッドにしています。

public static class SKProductExtension
{
    public static string GetLocalizedPrice(this SKProduct product)
    {
        var priceString = default(string);
        using (var formatter = new NSNumberFormatter())
        {
            formatter.FormatterBehavior = NSNumberFormatterBehavior.Version_10_4;  
            formatter.NumberStyle = NSNumberFormatterStyle.Currency;
            formatter.Locale = product.PriceLocale;
            priceString = formatter.StringFromNumber(product.Price);
        }
        return priceString;
    }
}

Xamarin.iOSではなくObjective-Cでの実装例ですが、StoreKit自体の実装のヒントには是非「 アプリ内課金+広告iPhoneプログラミング」をどうぞ。

アプリ内課金+広告iPhoneプログラミング

アプリ内課金+広告iPhoneプログラミング

Mac OS XでXamarin.iOSのクラスライブラリをNUnitを使ってコマンドラインでテストする

勘違いしていたので、以下取り消します。

Mac OS XでXamarin Studioを使ってXamarin.iOSでiOSアプリを開発していると、Jenkins等のCIツールを使ってビルドの自動化をする際にコマンドライン(ターミナル)からユニットテストを動かしたい要望が出てきます……よね?

本記事では、 Mac OS XでXamarin.iOSのクラスライブラリをコマンドラインでテストする方法を紹介します。

/Users/ch3cooh/Projects/Sample/UnitTest1/bin/Debug/にビルド済みのユニットテストプロジェクト(NUnit)のdllがあることを前提としています。

nunit-console /Users/ch3cooh/Projects/Sample/UnitTest1/bin/Debug/UnitTest1.dll

または以下のコマンドを実行してください。

mono /Library/Frameworks/Mono.framework/Versions/3.12.0/lib/mono/4.5/nunit-console.exe \
/Users/ch3cooh/Projects/Sample/UnitTest1/bin/Debug/UnitTest1.dll

コマンド実行後、ターミナルには下図のようにテスト結果が表示されます。

f:id:ch3cooh393:20150213231529p:plain

NUnit version 2.4.8
Copyright (C) 2002-2007 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment - 
   OS Version: Unix 14.1.0.0
  CLR Version: 2.0.50727.1433 ( 3.12.0 ((detached/de2f33f Mon Feb  2 14:42:07 EST 2015) )

.
Tests run: 1, Failures: 0, Not run: 0, Time: 0.019 seconds

WindowsでVisual Studioを使ってXamarin.iOSプロジェクトのクラスライブラリをコマンドライン(コマンドプロンプト)でテストする方法についてはまた後日調べたいと思います。

Xamarin.iOSでMD5やSHA1のハッシュ値を計算する

MD5(Message Digest Algorithm 5)は、入力に対して128ビットのハッシュ値を出力するハッシュ関数のことで、不可逆的な一方関数を用いているところから認証などで広く使われています。他にもダウンロードしたファイルが第三者によって作者(配布者)が意図しない改竄がおこなわれていないかの調査に使用することもあります。

本記事では、Xamarin.iOSでMD5やSHA1のハッシュ値を得るためにはどうすれば良いのかご紹介します。

MD5のハッシュ値を取得する

var input = "CH3COOH";

var md5 = System.Security.Cryptography.MD5.Create();
var bytes = System.Text.UTF8Encoding.UTF8.GetBytes(input);
var hash = md5.ComputeHash(bytes);

SHA1のハッシュ値を取得する

var input = "CH3COOH";

var md5 = System.Security.Cryptography.MD5.Create();
var bytes = System.Text.UTF8Encoding.UTF8.GetBytes(input);
var hash = md5.ComputeHash(bytes);

この値が正しいかどうかは、オンラインのハッシュ変換サイト等を使って検証することができます。

関連記事

Xamarin.iOSを使ったアプリを開発中に気付いたことや調べたことをメモしています。Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。

Xamarin.iOSを使ってiOSデバイスの機種名を取得するライブラリを作りました

過去に「Xamarin.iOSでiOSデバイスの機種名(モデル名)を取得する - 酢ろぐ!」でiOSデバイスの機種(モデル)名を取得する方法を紹介しました。この方法は機種名を取得するものと言うよりかはiPhone7,1といったデバイスモデルの識別子を取得する方法でした。

このデバイスモデルの識別子をiPhone 6 Plusといった人間にとってわかりやすい機種名に変換してくれるXamarin.iOS向けのライブラリを作りました。

UIDeviceHardwareExtensionsを作りました

ソースコードはUIDeviceHardwareExtensions.csの1ファイルだけなのでコピペして使ってください。

使い方

デバイスモデルの識別子を取得する。

var platform = UIDevice.CurrentDevice.GetPlatform();

機種名を取得する。

var modelName = UIDevice.CurrentDevice.GetPlatformName();

Reference

関連記事

Xamarin.iOSを使ったアプリを開発中に気付いたことや調べたことをメモしています。Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。

Xamarin.iOSでHtml Agility Packを使ってHTMLをスクレイピングする

この記事は「Xamarinのカレンダー | Advent Calendar 2014 - Qiita」の10日目です。

Xamarin Advent Calendarは、Xamarinを使ったアプリ開発のTipsが1日1つずつ紹介されています。この記事はXamarin.iOSについて書いていますが、他の方はXamarin.AndroidやXamarin.Formsなどの記事を書かれており大変有益なTipsが集まっています。

この記事では、HTMLを取得して解析して特定のデータを取得する方法を考えてみましょう。

ウェブスクレイピングとは

さて、本題に入る前にスクレイピングとは何でしょうか。うーん……悩んだときにはWikipedia先生に聞いてみましょう。

ウェブスクレイピングは多くの検索エンジンによって採用されている、ボットを利用してウェブ上の情報にインデックス付けを行うウェブインデクシングと密接な関係がある。(中略)ウェブスクレイピングの用途は、オンラインでの価格比較、気象データ監視、ウェブサイトの変更検出、研究、ウェブマッシュアップやウェブデータの統合等である。

なるほど。スクレイピングとは非構造化データ(HTML)から有益な情報を取り出して利用するということですね。

他にも触れていないことも沢山ありますが、ウェブスクレイピングについてはもっと詳しく知りたい方は「http://bugrammer.hateblo.jp/entry/2014/08/29/141926」をご参照ください。

本記事で紹介すること

本記事で紹介することは以下の通りです。

  1. Xamarin Studioでサンプルアプリ用のプロジェクトを作る(前準備)
  2. Html Agility PackをXamarin.iOSで使えるようにする
  3. スクレイピング処理を実装する

さぁ、Xamarin.iOSでスクレイピング処理するアプリを作ってみましょう。

前準備

前準備ではスクレイピング処理とは関係ない部分に関してさっくりと紹介してしまいます。Xamarin Studioで新しいプロジェクトを作成する方法はこちらをご覧ください。

まずは、Xamarin Studioを起動してプロジェクトを適当に作成します。ここでは、新しいソリューションダイアログの名前(プロジェクト名)欄に「KabukaApp」と入力しました。

f:id:ch3cooh393:20141205210400p:plain

[OKボタン]をクリックするとテンプレートプロジェクトが生成されました。

ボタンとラベルを配置します

Xamarin Studioには、Xcodeを起動しなくてもStoryboardを編集することのできる「iOS Designer」が搭載されています。

左ペインにあるソリューションエクスプローラーからMainStoryboard.storyboardをダブルクリックしてiOS Designerで表示します。Single View Applicationを選択して生成したプロジェクトのRoot View Controllerは白紙の状態ですので、スクレイピング処理を開始するトリガーとなるUIButton(ボタン)スクレイピングした結果を表示するUILabel(ラベル)をそれぞれ配置しましょう。

右ペインのツールボックスからUIButtonとUILabelをドラッグアンドドロップします。適当な位置にViewを配置して、Viewのサイズを調整します。

f:id:ch3cooh393:20141205211935p:plain

ラベルに名前をつけます。右ペインにプロパティウィンドウが表示されているので、WidgetタブのName欄にViewの名前を入力します。名前を入力するとKabukaAppViewController.designer.csにプロパティが追加されます。

f:id:ch3cooh393:20141205213741p:plain

ボタンは既に追加していると思います。スクレイピング処理を開始するトリガーとなるボタンをタップした時のイベントを追加しましょう。プロパティウィンドウのEventタブを選択します。Up InsideにTappedStartという名前を入力します。

f:id:ch3cooh393:20141205213808p:plain

KabukaAppViewController.designer.csを見てみましょう。.designer.csファイルは基本的にXamarin Studioによって自動生成されたコードなので普段は見る必要のないファイルです。Objective-Cを使ってiOSアプリを開発されていた方にはご存知だと思いますが、IBOutletIBActionなどの修飾子は、Xamarin(C#コード)上では属性として定義されています。

// WARNING
//
// This file has been generated automatically by Xamarin Studio from the outlets and
// actions declared in your storyboard file.
// Manual changes to this file will not be maintained.
//
using System;
using Foundation;
using UIKit;
using System.CodeDom.Compiler;

namespace KabukaApp
{
  [Register ("KabukaAppViewController")]
  partial class KabukaAppViewController
  {
    [Outlet]
    [GeneratedCode ("iOS Designer", "1.0")]
    UILabel OutputLabel { get; set; }

    [Action ("TappedStart:")]
    [GeneratedCode ("iOS Designer", "1.0")]
    partial void TappedStart (UIButton sender);

    void ReleaseDesignerOutlets ()
    {
      if (OutputLabel != null) {
        OutputLabel.Dispose ();
        OutputLabel = null;
      }
    }
  }
}

TappedStart(UIButton)メソッドがpartialで宣言されていますので、実体を実装するためにKabukaAppViewController.csを開きます。

using System;
using System.Drawing;
using Foundation;
using UIKit;

namespace KabukaApp
{
  public partial class KabukaAppViewController : UIViewController
  {
    public KabukaAppViewController (IntPtr handle) : base (handle)
    {
    }

    // ボタンを押したらこのメソッドが呼ばれる
    partial void TappedStart (UIButton sender)
    {
      this.OutputLabel.Text = "ボタンが押されました";
    }

// .....

ここまで実装できれば一度iPhoneシミュレータで実行してプロジェクトを正常にビルドすることが可能か確認します。実行するiPhoneシミュレータのデバイスを選択します。特に理由はありませんが、ここではiPhone 4s iOS 8.1を選択します。

f:id:ch3cooh393:20141205214717p:plain

Xamarin Studioのウィンドウ左上にある▶ボタン(実行ボタン)をクリックします。少し時間はかかりますがiPhoneシミュレータが起動してアプリがデプロイされます。アプリが起動されたらボタンをタップします。

f:id:ch3cooh393:20141205214724p:plain

ラベル部分に「ボタンが押されました」と表示されると思います。はい、うまいこと行きましたね。

ウェブスクレイピングをおこなうC#ライブラリ

Html Agility Pack」という.NET Framework向けに書かれたHTMLパーサーライブラリがあります。

このライブラリはC#から利用することができます。元々はデスクトップPC用として作られていたライブラリなのですが、この記事ではDeathspikeさんによってPortable Class Library(PCL)用に移植された「HtmlAgilityPack-PCL」を利用することにします。本家の方と比べメソッドがいくつか存在しませんが機能的には十分です。

HtmlAgilityPack-PCLは、NuGet経由でインストールすることができます。しかし、残念なことに実行時にエラーが発生してしまうため僕の環境では利用できませんでした*1 *2

HtmlAgilityPack-PCLを導入する

Xamarin.iOS (Unified API)プロジェクトから利用しやすいように、iOS用のライブラリプロジェクトを作成して、HtmlAgilityPack-PCLのソースファイルを追加して みましょう。*3

GitHubからソースコード一式を任意のフォルダへダウンロードします。

f:id:ch3cooh393:20141208171540p:plain

.zipファイルを解凍します。ソースファイルは下図の通りです。.csprojファイルや.slnファイルなどのプロジェクト関係以外のファイルをそのまま利用したいと思います。

f:id:ch3cooh393:20141208171618p:plain

左ペインのKabukaAppを選択して右クリックしてコンテキストメニューを表示します。[追加]、[新しいプロジェクトを追加...]の順に選択して、新しいプロジェクトを作成します。

f:id:ch3cooh393:20141208171650p:plain

新しいプロジェクトダイアログが表示されるので、Unified APIのiOS Library Projectを選択します。プロジェクト名はなんでも良いですが、ここではHtmlAgilityPack-PCLとつけてみました。

f:id:ch3cooh393:20141208171727p:plain

先ほど解凍したソースファイルを追加します。

f:id:ch3cooh393:20141208171802p:plain

対話ウィンドウが表示され、追加の仕方を選択します。Copyを選択して[OK]ボタンをクリックします。

f:id:ch3cooh393:20141208171916p:plain

そうそう、PCLフォルダ直下のファイルもプロジェクトへ追加することを忘れないでください。

f:id:ch3cooh393:20141208171833p:plain

実際にビルドして、コンパイル時にエラーが発生しないことを確認します。

f:id:ch3cooh393:20141208171941p:plain

これでXamarin.iOS (Unified API)プロジェクトから利用するためのライブラリプロジェクトへのHtmlAgilityPack-PCLの移植は完了です。

参照の追加

アプリプロジェクトであるKabukaAppプロジェクトから、ライブラリへの参照を追加します。

f:id:ch3cooh393:20141208172026p:plain

以上で、HtmlAgilityPack-PCLの導入が完了しました。

Yahoo!ファイナンスから現在の株価を取得する

@kazuakixと株の話をしている時に欠かせないのはイオン(8267.T)です。以前Tipsを紹介したことがあるYahoo!ファイナンスの株価を表示させてみましょう。前述したTappedStartメソッドの処理を書き換えてしまいましょう。

// ボタンを押したらこのメソッドが呼ばれる
partial void TappedStart (UIButton sender)
{
    // 株価を取得したいサイトのURL
    var code = "8267.T";
    var urlstring = string.Format("http://stocks.finance.yahoo.co.jp/stocks/detail/?code={0}", code);

    // 指定したサイトのHTMLをストリームで取得する
    var doc = new HtmlAgilityPack.HtmlDocument();
    using (var client = new System.Net.WebClient())
    {
        var html = client.DownloadString(new Uri(urlstring));

        // HtmlAgilityPack.HtmlDocumentオブジェクトにHTMLを読み込ませる
        doc.LoadHtml(html);
    }

    // XPathを指定し株価部分を取得する
    var priceNode = doc.DocumentNode.Descendants("td")
        .Single(node => node.GetAttributeValue("class", "") == "stoksPrice");

    // 取得した株価がstring型なのでint型にパースする
    var price = int.Parse(priceNode.InnerText,
        NumberStyles.Integer | NumberStyles.AllowThousands, 
        NumberFormatInfo.CurrentInfo);

    this.OutputLabel.Text = string.Format("イオン(8267.T)の株価: {0}円", price);
}

上記のサンプルコードを実行します。いかがでしょうか?

f:id:ch3cooh393:20141209225840p:plain

まとめ

Xamarin.iOSでHtml Agility Packを使ってHTMLをスクレイピングする方法をご紹介しました。Objective-Cで同様のコードを書く場合には、若干骨が折れる作業になるのでそれよりかは簡単に導入できたのではないでしょうか(当社比)。

このブログでは12月に入ってからチマチマとHtml Agility Packを使ったTipsを書いています。一通りの使い方はこの記事で紹介しきってしまった感がありますが、もし「ほかのシーンではどのように使えばよいのか?」といった疑問があれば参考にしていただければと思います。

明日の「Xamarinのカレンダー | Advent Calendar 2014 - Qiita」の担当は鈴木章太郎(shosuz)さんです。鈴木さんはコミュニティ勉強会等でAzure Mobile Servicesについてお話していたのを何度か聞いたことがあります。明日はどんな記事が公開されるのか今から楽しみですね❤️

追記(2014/12/17)

PCLではなくて、本家の「Html Agility Pack」をXamarin.Androidでビルドする方法が紹介されました!こっちのが簡単!

関連記事

Xamarin.iOSを使ったアプリを開発中に気付いたことや調べたことをメモしています。Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。

このブログでHtml Agility Packについて取り扱った記事をピックアップしました。

*1:dllが見つからないのか実行しようとするとFileNotFoundExceptionでアプリがクラッシュする

*2:先週に試した時には、NuGet経由でHtmlAgilityPack-PCLをプロジェクトへ導入できることを確認してたと思ったんだけど、週明けてスクリーンショットを取ってる時には使えなくなってました。Xamarin Studioをアップデートしたせいか、そもそも僕の記憶違いか……。一度、NuGetを使って導入する記事を書いてたのですが、一部削除して自前でライブラリをビルドする方法に書き直しました

*3:元々、PCL向けにビルドされているものなのですが、.slnプロジェクトを開いてもターゲットが古いのでビルドを通すのが面倒くさそうなので断念しました

Xamarin.iOSで新しいプロジェクトを作成する(Unified API)

過去にXamarin.iOSとXamarin.Macは別々のコードでメンテナンスされていて、さらに64bit対応されていませんでした。それを受けて、2014年9月に品質管理と64bit対応のため統合された「Unified API」が登場しました。過去のものは「Classic API」という位置付けになりました。詳しくはミゲル氏が書かれた下記の記事をご参照ください。

Appleは、2015年2月1日以降 iOS 8と64bit対応されていないiOSアプリの新規申請とアップデートを受け付けないと発表しており、新しく開発が始まるプロジェクトに関しては、今後のことを考えると「Unified API」を使った方が良いでしょう。ただ現時点ではいくつかのXamarin Componentsに登録されているコンポーネントがClassic APIで実装されており、Unified APIプロジェクトでは使えないこともあるので注意が必要です。

本記事では、Xamarin Studioを使ってXamarin.iOSのプロジェクトを作成する方法を紹介します。Xamarin Studioを起動します。

左側に「New Solution...」と書かれたボタンをクリックします。

f:id:ch3cooh393:20141204174906p:plain

新しいソリューションダイアログが表示されるので、左側のリストからC#iOSUnified APIiPhoneSingle View Applicationの順に選択します。任意の名前を入力して、[OK]ボタンをクリックすると新しいソリューション(とプロジェクト)が生成されます。

f:id:ch3cooh393:20141204174930p:plain

関連記事

Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。

Xamarin.iOSでサイズを指定して空の(または単色の)UIImageオブジェクトを生成する

本記事では、Xamarin.iOSを使ってUIImageオブジェクトをプログラム(コード)だけで動的に生成する基本となる方法を紹介します。Objective-Cで実現する方法については下記の記事をご覧ください。

以下、サイズを指定して空の(または単色の)UIImageオブジェクトを生成する方法をご紹介します。

private UIImage CreateImage(CoreGraphics.CGSize size,
    CoreGraphics.CGColor fillColor = null)
{
    UIImage image = null;

    // ビットマップ形式のグラフィックスコンテキストの生成
    UIGraphics.BeginImageContextWithOptions(
        size, true, 0);

    var context = UIGraphics.GetCurrentContext();

    // 指定した色で塗りつぶす
    if (fillColor != null)
    {
        context.SetFillColor(fillColor);
        context.FillRect(new CoreGraphics.CGRect(
            new CoreGraphics.CGPoint(0, 0), size));
    }

    // 現在のグラフィックスコンテキストの画像を取得する
    image = UIGraphics.GetImageFromCurrentImageContext();

    // 現在のグラフィックスコンテキストへの編集を終了
    // (スタックの先頭から削除する)
    UIGraphics.EndImageContext();

    return image;
}

このCreateImageメソッドは以下のように使用します。以下のサンプルコードではGetCellメソッドで使っていることとしましょう。

var image = CreateImage(
    new CoreGraphics.CGSize(50, 50), UIColor.Purple.CGColor);
    
...

cell.ImageView.Image = image;

UITableViewCellに設定されたImageViewは下図のようになります。見ての通り、50pt x 50ptの単色で塗られたUIImageができました。

f:id:ch3cooh393:20141126231003p:plain

このTipsは、UIGraphicsを使って画像を生成する上でもっともシンプルなコードです。

関連記事

Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。