swift – itunes store にサインイン画面が何度も表示される

iPhoneアプリ開発中にStoreKitの購入処理をデバッグしていたら、アプリ起動時にサインインを求められる画面が表示されるようになった。これが表示されるようになった原因は、直前の購入処理がうまくいかず、購入の処理が終わってないことが原因だと思われる。トランザクションの終了処理が未実行となっているのだと予想。

だが、アプリ起動時にいきなりストアへのサインイン画面が呼ばれるというのがわからない。何のメソッドが呼ばれてサインイン画面が表示されているのかというのがわからないので手の打ちようがない。アカウントをテストユーザーでサインインしていたが、別のテストユーザーでサインインしたところ起動時のサインイン画面には前のユーザー名が表示されて消えないという現象も確認した。

現在のトランザクションの数を数えてみる。下記がレストア終了した後に呼ばれるメソッドでキューの数をコンソールに表示してみたら6という数字になった。おそらくこれが原因で起動時にサインイン画面が表示されていたのではないかと思う。

    func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {

        print(queue.transactions.count)

    }

溜まったトランザクションに対して終了処理を実行する。とりあえず上記メソッドに一回だけ下記のようなコードを追加・実行する

    func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) {
        
        for transaction:SKPaymentTransaction in queue.transactions{
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        }
        
        print(queue.transactions.count)
    }

上記を実行すると溜まっていたトランザクションはとりあえず消える。で、再度アプリを起動させてみたところ、やはりサインイン画面は消えなくて相変わらず表示される。その後サインアウトしたり別のアカウントでサインインしてみたりとやっているうちに、起動時に表示されていた意味不明のサインイン画面は表示されなくなった。結局、明確な解決方法はわからずじまい。未終了のトランザクションが溜まっていてそれを削除したのは関係がありそうだけれども、なんとも言えない。

xcode – appstore – file must contain a higher version than that of the previously uploaded version

This bundle is invalid. The key CFBundleVersion in the Info.plist file must 
contain a higher version than that of the previously uploaded version.

バージョン1.13のバイナリを新しくアップデートしてバージョン1.2をapp storeに登録しようとしたら上記エラーが出て怒られた。appstoreの解釈では1.13 > 1.2ということになっているらしい。不本意ながら仕方がないのでバージョンを1.20としてビルドしてアップロードしたらエラーなく通った。

swift – SKNodeからSKSpriteNodeへ変換

SKViewからnodeAtPointで取得したノードはSKNodeとして受け取るため、それをSKSpriteNodeにキャストする。Objective-cの時はAnyObjectに突っ込んでからキャストしてればよかったがswiftだと型の判定が厳しくなっている模様

下記コードはSKNodeからSKSpriteNodeへ変換・・・しているというわけではない。SKSpriteNodeであると確信があるので決め打ちしてキャストしているだけ。

サンプルコード

override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
        
        for touch: AnyObject in touches {
            
            var touchPos: CGPoint = touch.locationInNode(self)
            var node = self.nodeAtPoint(touchPos)

            if (node.name == "test"){
                        let sknode :SKSpriteNode = (node as? SKSpriteNode)!
            }
        }
}

swift – app内課金の実装

itunes connectやdeveloper centerで下準備は済ませているものとして。コードの実装部分のみ。課金の商品は1度きりの購入のよくある広告非表示の商品をイメージ。
処理の流れとしては、viewDidLoadにて初めの画面表示の時に製品IDで商品情報を取得して、ボタンを押したら購入処理を開始するというもの。商品が一つだけなら下記のような感じにできるが、普通は複数でテーブルビューなどに表示して配列で商品管理をやっている模様。出来るだけ簡単で少ないコードでの実装を目指す・・・

購入処理

デリゲートの追加

class ClassName: UIViewController SKProductsRequestDelegate, SKPaymentTransactionObserver{

}

必要なデリゲートメソッド

func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

}

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){

}

実装例

import UIKit
import StoreKit

class ClassName: UIViewController SKProductsRequestDelegate, SKPaymentTransactionObserver{

var storeBt:UIButton!
var productID = “com.sample.product100”
var vProduct: SKProduct!

override func viewDidLoad() {

storeBt = UIButton()
storeBt.frame = CGRectMake(0, 0, 200, 22)
storeBt.setTitle(“”, forState: UIControlState.Normal)
storeBt.setTitleColor(UIColor.orangeColor(), forState: UIControlState.Normal)
storeBt.setTitle(“”, forState: UIControlState.Highlighted)
storeBt.setTitleColor(UIColor.blackColor(), forState: UIControlState.Highlighted)
storeBt.titleLabel!.adjustsFontSizeToFitWidth = true
storeBt.addTarget(self, action: “byAc:”, forControlEvents: .TouchUpInside)
self.view.addSubview(storeBt)

SKPaymentQueue.defaultQueue().addTransactionObserver(self)
getProductInfo()

}

func getProductInfo(){
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers:
NSSet(objects: self.productID) as! Set)
request.delegate = self
request.start()
} else {
print(“Please enable In App Purchase in Settings”)
}

func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

let count : Int = response.products.count
if (count>0) {
var validProducts = response.products
var validProduct: SKProduct = response.products[0] as SKProduct
if (validProduct.productIdentifier == self.produto_value) {
print(validProduct.localizedTitle)
print(validProduct.localizedDescription)
print(validProduct.price)
vProduct = validProduct

//
storeBt.setTitle(“\(validProduct.localizedTitle) \(validProduct.price)円”, forState: UIControlState.Normal)
storeBt.setTitle(“\(validProduct.localizedTitle) \(validProduct.price)円”, forState: UIControlState.Highlighted)

} else {
print(validProduct.productIdentifier)
}

} else {
print(“not found”)
}
}

func byAc(sender: AnyObject){

if(vProduct != nil){
let payment = SKPayment(product:vProduct)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
}

func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){

for transaction: SKPaymentTransaction in transactions {
switch transaction.transactionState {
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “失敗”)

case .Purchasing:
NSLog(“%@”, “購入中”)

case .Restored:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “リストア”)

case .Purchased:
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
NSLog(“%@”, “成功”)

// レシートを取得して検証
var receiptURL: NSURL = NSBundle.mainBundle().appStoreReceiptURL!
var receiptData: NSData = NSData(contentsOfURL: receiptURL)!
var base64receiptData: String = receiptData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
NSLog(“%@”, base64receiptData)

default:
print(transaction.transactionState.rawValue)
break
}
}
}
}

touchesBeganとtouchesEndedの間にある遅延について

iOSでtouchesBeganイベントが呼ばれてtouchesEndedが呼ばれるまでの間に約0.4秒ほどの遅延があることがわかった。(マシンの調子が悪い時は0.5秒ほど)
touchesBeganからtouchesMovedイベントまでの遅延はほとんど気にならない程度。
この0.4秒の遅延は体感ではまあ普通のアプリでは反応悪いなと許容できる範囲の時間ではあるけれども、応答の即時性が求められるアプリではちょっと長すぎる。この時間をなんとかゼロにできないものかといろいろ調べてみたが、見つからなかった。touchesEndedイベントに必要なディレイ時間として設定されていてプログラミングでプロパティを変更とかできないものかもしれない。
例えば画面をポンッとタップしてその結果がすぐにわかる必要があるような実装をする場合、処理をtouchesEndedに書いていたら0.4秒の遅延が発生するので使えない。この操作だとtouchesMovedイベントも呼ばれない。仕方がないのでtouchesBeganだけで完結するような実装にすることにした。

四国銀行がオンラインバンキングでハードウェアトークンを始めた様子

ワンタイムパスワード(ハードウェアトークン)の取扱い開始 | 四国銀行

四国銀行が振り込みと振替をオンラインバンキングでする場合にはワンタイムパスワードを必須にする様子。ハードウェアで短時間だけ有効なパスワードを生成する仕組みで、はっきりいうとジャパンネット銀行のパクリである。ハードウェアトークンの見た目もシール張り替えただけのそのままのように見える。

ワンタイムパスワード(ハードウェアトークン)とは

パスワード生成機(ハードウェアトークン)により生成・表示される1分ごとに変化する使い捨てのパスワードです。ワンタイムパスワードは1分後には使えなくなることから、第三者に遠隔操作等で不正にログインされるリスクを低減することができます。

振り込みの時にこのワンタイムパスワードを使うらしいから不正ログインのリスクとは関係がないような気がするが・・・。個人的には四国銀行のオンラインバンキングでこんなハイテクそうなことをやって大丈夫なのかという心配がある。昔の四国銀行のオンラインバンキングのUIは素人丸出しで絶対ホームページ・ビルダーで作ってるだろこれと思いながら私は使っていた。それを知っているからか残高照会以外の取引を四国銀行のオンラインバンキングでやるのはちょっと怖い。オンラインバンキングのアドレスを見るとwww.parasol.anser.ne.jpというドメインで運用されている様子でセキュリティ証明書はNTT DATA となっている。ジャパンネット銀行みたいなオンライン専門の銀行じゃないんだからあまり無茶はしないほうがいいとおもうんだけれども。

それにしても、ジャパンネット銀行をオンライン決済で利用している私はこのトークンをすでに使っていて、さらに四国銀行のトークンも管理しなければならないというのは、なんかセキュリティのためとはいえ馬鹿っぽい気がする。私は他にゆうちょ銀行や楽天銀行の口座も持っているが、それらの銀行がもしもトークンの利用開始を始めてしまったらたまったもんではない。財布に溜まっていくコンビニや量販店のポイントカードじゃあるまいし。

メインビューとサブビューの座標の違い

メインビューでaddsubviewにてuiviewを追加して、ボタンを押したら別のビューが下からビョーンって出てくるようなものを考えていたが、座標の問題で困った。メインビューの座標とサブビューの座標はそれぞれ存在しているため、メインビューでタップしたポジションでサブビュー上のパーツを動かそうとしたら、当然おかしなことになる。このサブビューを下から少しだけ飛び出すような使い方だと座標の変換が多分必要になる。あと、サブビューのパーツを画面外のメインビューへ飛び越えるような使い方は、多分無理な予感。試してはないけれども、普通の感覚では不自然。

下からビューを飛び出してきてその上のパーツをいろいろ動かすのは、擬似的にすることにした。つまりビューだけ下から表示して、その上のパーツはメインビューにaddsubview()するということ。つまり見た目の上ではテクニカルなことをなんかやっているように見えるだけ。たいていの場合こういうのは設計が悪くて他にもっとスマートな解決方法があるはず。なんか無理やり実装してる感が出てきたら経験上よろしくない。後々のメンテナンス性が劣悪になったりとか。