2018年8月10日金曜日

Twitterアプリがないと他のアプリからツイートできない?!

問題

TwitterKitでは、Twitter公式アプリをインストールしていないデバイスで、別のアプリからツイートを投稿しようとしても、以下のようなエラーを吐いて認証ができなくなってしまった。

2018-08-10 22:19:59.259526+0900 アプリ名[72348:10180750] [discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}
2018-08-10 22:20:01.430469+0900 アプリ名[72348:10180372] [TwitterKit] Error obtaining user auth token.

たとえばXcodeのiPhoneシミュレータでテストしようとしても、いちいち公式アプリがないといけないことになる。

原因

これはツイッター社による仕様変更のためで、ここにあるように、以前はツイッターにアプリ登録の際、
https://example.com
ってごまかしていた Callback URL なるものをきちんと登録しないといけないという。

解決法

Callback URLってナニ? ってのが本音なのだが、時間がないので解決法方だけ記す。
TwitterKitを使用する際にTwitterのApplication Managementサイトでアプリの登録を行ったわけだが、新規登録の際に得られたConsumer Key (API Key) を使った擬似的なURLを書けばOKだ。



Consumer Keyが ABCDEFGHIJK だとすれば、Settingsタブで
https://example.com
のかわりに
twitterkit-ABCDEFGHIJK://
と書けばいい。
そして下のUpdate Settingsボタンを押して完了。
これですぐにアプリから認証ができるようになる。
簡単だけどめんどくさいな。

参考サイト:TwitterがインストールされていないとTwitterKitで認証できない

2018年8月4日土曜日

TwitterKitが変更された

気づいたらTwitterKitが3.4.0にアップデートされていた。公式サイト
^ <>\|`など、一部の文字がツイートできない問題も解決しているらしいので、さっそくインストールした。(後述するが、\ (バックスラッシュ)だけはいまだツイートできなかった)

Frameworkをインストール



公式サイトに書かれた通りにやる。
  1. TwitterKit.frameworkをEmbedded Binariesに追加
  2. TwitterKit.frameworkとTwitterCore.frameworkをLinked Frameworks and Librariesに追加
とある。
しかし1.ですでにTwitterKit.frameworkはLinked Frameworks and Librariesにも自動で追加されるているので、TwitterCore.frameworkだけでいいと思われる……と思ってあらためてLinked 〜 に追加しなかったところ、AppStoreに提出する前のValidate...時に以下のエラーが出て悩むことになった。
1.のEmbedded 〜 への追加はしてもしなくても良くて、2.のLinked 〜 にTwitterKitを追加してやったら直った。注意が必要だ。

TwitterKitResources.bundleも必要



公式サイトに書かれてないけど、TwitterKitのフォルダに入ってるTwitterKitResources.bundleというエイリアスファイルもそのままAdd Files to プロジェクト名 してやらないといけない。

bridging headerが必要

これまではSwiftではふつーに
import TwitterKit
としておけば良かったが、Objective-C用しか用意されていないらしく、Objective-C bridging headerを作らねばいけなくなった。
作り方はObjective-CとSwiftのコードを混在させる方法を調べてもらうとして、その中で
#import <TwitterKit/TWTRKit.h>
としなければならない。

Twitter --> TWTRTwitterに書き換える

Twitter.sharedInstance().application(app, open: url, options: options)
などと書いていたののTwitterの部分をTWTRTwitterと書き換える必要がある。
まあこれは簡単。

Linker Errorが出る


ここまでで完璧と思ってさっそくビルドしてみたところ、Apple Mach-O Linker (ld) Errorなどというエラーが出てしまった。
必要なFrameworkがインストールされていないか、必要ファイルへのpathが狂っているなどの場合に出るらしい。

MKMapViewとかMKMapView、_kUTTypeImageなどというメッセージからMapKit.frameworkとMobileCoreServices.frameworkが必要と判断して追加したら消えてくれた。
そんなことどこに書かれてんだよ。ツイッター、この野郎。

とりあえずビルドできるようになったのは幸い。

バックスラッシュだけはまだ送信できない

上述の通り、^ < >など一部の文字が含まれているとツイートできない問題は直っていたが、\(バックスラッシュ)だけはいまだツイートできなかった。
\(^o^)/ などというように顔文字で使われることがあるから早く直してくれないと困るな。制御文字を扱う際のprefixに使われてるせいじゃないかと思うが、ツイッター公式アプリやWeb版のツイッターじゃ問題ないんだからツイッター社の怠慢。

2018年7月8日日曜日

OpenCVを使う(覚書)

OpenCVはインテルが開発したオープンソースライブラリで、画像処理に定評があるという。
SOURCEFORGEというサイトで入手できる。

Swiftから使う方法

C++で書かれているらしく、Swiftから直接扱えないので、Objective-C++を経由して扱う。
簡単に書くと、

  • SwiftのプロジェクトにWrapper(間を取り持ってくれるクラスだ)となるObjective-Cのクラスを作る(ex. OpenCVWrapper.hと同.m)
  • その過程でSwiftとObjective-Cのクラスを同居させるためのBridging-Header.hを作る
  • Wrapperクラスとして作ったObjective-Cのファイル「クラス名.m」の拡張子を、Objective-C++の拡張子の「クラス名.mm」に変更
  • Bridging-Header.hに #import "OpenCVWrapper.h"
  • Wrapperクラスのヘッダファイルに #import <UIKit/UIKit.h>
  • Wrapperクラスの実装ファイル(さっき.mmにしたとこ)には以下のように
    • #import <opencv2/opencv.hpp>
    • #import <opencv2/imgcodecs/ios.h>
    • #import "OpenCVWrapper.h"
  • Wrapperクラスの中にOpenCVにアクセスするObjective-C++のメソッドを書き、Swiftからそのメソッドを使うことで機能を使用する
……らしい。
まだよくわかってないけど。

いっぱい出るWarningを消す

こんなのがいっぱい出た
Xcodeでそのままコンパイルしたら、
  • Not a Doxygen trailing comment
  • Empty paragraph passed to '@note' command
などというよくわからないWarningが86個も出てしまった。

気になるならここをNoに
stackoverflowでは「無視してもいいが、Build SettingsのDocumentation CommentsをNoにすればいい」とあったんで、試したらほんとに消えた。こちらのサイトでも。
iOS、Xcodeの必要なメッセージまで消えちゃうかどうかは知らん。

IplImageに変換せねば…

OpenCVで使われてる画像管理の構造体がIplImage。
iOSの画像をこいつに変換してやらないといろいろ使えないのが面倒。
調べたところ、CGImageからIplImageへの変換方法は一筋縄でいかず、Objective-C++でIplImageの変数とポインタを共有するContextを作ってそれに描画するとか、色空間を変換するとか、やたら複雑。
他のやり方がわからないので丸コピーするみたいな感じになってしまった。

特定の色を透明にする

やりたいこと

画像の特定の色(例えばグレーの部分とか)だけを透明にしたい。
(別な画像と合成する時に使うのだ)

やり方

let maskColor:[CGFloat] = [188, 188, 188, 188, 188, 188]
let maskedImage = cgImage?.copy(maskingColorComponents: maskColor)


Core Graphicsの機能を使うので、画像をCGImageに変換しておく。(例では変数cgImageとして用意)

maskColorの配列が、透明化したい色を範囲として指定するための引数。はっきりはわかってないんだけど、要素0から順に、Rの下限、上限、Gの下限、上限、Bの下限、上限を0〜255の値で指定するようだ。
一色だけなら全部同じ値にすればいいし(CIFilterのポスタリゼーションで作った3階調のグレー色は188だった)、色の範囲で指定するならそれぞれの下限値、上限値を変えればいい。

実際に透過処理をしてるのがCGImageのcopyメソッド。処理が終わったCGImageを返す。
合成処理をするならCGImageのままかCIImageがいい。というか、macOSでNSImageに変換したら透明じゃなくなってしまった。

リファレンス見ると最後にCGImageをreleaseしろって書いてあるんで、そうした方がいいみたい。

注意

元画像にαチャンネルがあるとnilが返ってきてしまうので、あらかじめα情報を取り除く作業をしておかないといけない。そうしないと予期せぬエラーでアプリが落ちてしまう。

UIGraphicsの作り直しのOptionで第2引数のopaqueをtrueにすればOK。

UIGraphicsBeginImageContextWithOptions(srcImage.size, true, 0.0)
srcImage.draw(in: CGRect(origin: CGPoint.zero, size: srcImage.size))
let remakedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

2018年7月3日火曜日

年に一度の証明書の更新手順

手順の概要

  1. アップル・デベロッパーの証明書ページで古い証明書(DeveloperとDisributionをそれぞれ)を削除
  2. 新しい証明書作成開始
  3. Macのアプリ「キーチェーンアクセス」から証明書リクエストファイルを作成
  4. アップル・デベロッパーに証明書リクエストファイルを読み込ませる
  5. 新しい証明書ファイルをダウンロード
  6. 証明書ファイルをダブルクリックしてキーチェーンに追加

アップル・デベロッパーから、以下のタイトルのメールが届く

Action Needed: iOS Distribution Certificate Expires in 30 Days
(iOS配布用証明書があと30日で切れるで)

(以下のページデザインは2019年時点で別のものになってる。だいたいわかるけど、ちょこちょこ変えるなよな、アップルさんよお)

アップル・デベロッパーサイトのCertificates, Identifiers & Profiles ページを開く


この画面は証明書の一覧。
ここで期限切れ、もしくは期限切れ間近の証明書を選んで、Revoke(廃止)ボタンを押す。(消さなくても作れる場合もあるみたいだが)

DeveloperもしくはDistributionの証明書を作る

前の画面の右上にある「+」ボタンを押すと、以下の画面が出る。
Developmentが赤字で「証明書の数がいっぱい!」って書いてあるのは作った後のスクリーンショットだから。
DevelopmentはそのままDevelopmentの証明書、ProductionはDistributionの証明書なので(ややこしいな!)、それぞれを作ることになる(一度にはできない)。

画面下のContinueボタンを押すと以下の画面になる。
「キーチェーンアクセス.app」を使って必要な作業手順が書かれてる。

キーチェーンアクセス.appで証明書リクエストファイルを作る

ウェブサイトとMacのアプリを行ったり来たりしながら作業しなきゃいけないのはめんどくさいけど、しょうがない。

Macのキーチェーンアクセス.appを開く。
(ここで検索窓にiPhoneと入力し、古い証明書を消しとくといいみたい)

メニューの、キーチェーンアクセス/証明書アシスタント/認証局に証明書を要求...
を選ぶと以下の画面に。
ここで
「ディスクに保存」を選び(「鍵ペア情報を指定」もチェックしたほうがいいらしい?)「続ける」を押すと、
「CertificateSigningRequest.certSigningRequest」という証明書リクエストファイルを保存するダイアログが出るんで、わかるところに保存する。

デベロッパーサイトに戻る

キーチェーンアクセスの作業手順が書かれた画面でContinueを押すと以下の画面になるので、Choose File...から、今作ったCertificateSigningRequest.certSigningRequestファイルを指定してContinue。
(ここでアップロードするリクエストファイルは、DevelopmentとDistributionで同じものでいいようだ)

証明書をダウンロード

そうするとDevelopmentもしくはDistributionの証明書が作られるので、Download。
この作業を、必要であれば前述のとおりDevelopmentとProduction(Distribution)の2回やる。
できた証明書ファイルがこれ。
これをそれぞれダブルクリックすると、以下のようにキーチェーンに追加するか聞いてくるので、「追加」で終了。
これで証明書の更新作業が終わり。

2018年6月19日火曜日

Spriteの動きをランダムにしたい

やりたいこと

Spriteの動く方向を毎回ランダムに変えたい。
SKActionだけで完結したい。

やり方

以下のようにSKAction.customAction()を使ったらうまくいった。
繰り返しはrepeatForeverを使いたかったけど、まだよくわからんのでdurationで。

let spriteNode = SKSpriteNode(imageNamed: "イメージ名")
let furafura = SKAction.customAction(withDuration: 10.0) { (node, elapsedTime) in
    let x = CGFloat(arc4random_uniform(50)) - 25
    let y = CGFloat(arc4random_uniform(50)) - 25
    print("x \(x), y \(y)")
    node.run(SKAction.moveBy(x: x, y: y, duration: 1.0))
}
spriteNode.run(furafura)

うまくいかなかったやり方

SKAction.repeatForever()と組み合わせ、SKAction.run{}を使ってクロージャの中に同様のコードを書いたら、最初に通った時に発生した乱数の値で固定されてしまい、一方向にしか動かなかった。
そもそもrepeatForever()の書くところが難しく、変な書き方するとXcodeおよびiPhone実機でアプリが終了できなくなり、iPhoneがブラックアウト、強制再起動を余儀なくされたりとおっかないことになった。なんじゃこりゃ。
詳しくはこちら

2018年6月15日金曜日

AR画面にSpriteを表示したい

Xcode9のARのテンプレートに用意されてるが、それを学習する。
ARもSpriteもよくわかってないので、時々トンチンカンかもしれませんよ。

使用ファイル


  • Scene.sks
    • Spriteを表示するための場所。Viewみたいなものだろ。場所として持ってるだけで、InterfaceBuilder的には何も設定されてないっぽい。
  • Scene.swift
    • Scene.sksに結びついてるクラス
    • 表示されたSceneがtouchされたらどうのこうの…という処理をしてるようだ
  • ViewController.swift
    • Scene.sksをロードして表示する
    • AR画面にSpriteを表示するためのARSKViewのdelegateメソッドで実際にSprite(ここではLabelNodeで👾)を表示してる

Scene.swift

touchしたらカメラの向いた方向の、カメラ位置の20cm先にARAnchorを追加する。

ARAnchorとは

ARシーンにオブジェクトを配置するために使用する、実世界の位置と方向の情報。
カメラに対する実オブジェクトまたは仮想オブジェクトの位置と向きを追跡するには、アンカーオブジェクトを作成し、add(anchor :)メソッドを使用してそれらをARセッションに追加する。
ARKitは、ワールドトラッキングセッションでplaneDetection(平面探索)オプションを有効にすると、アンカーも自動的に追加される。
ARAnchorを追加すれば、カメラの向きを変えたり、近づいたり離れたりしても、その空間上の位置を覚えててくれるわけですな。それによってそこに配置したSpriteに近づいたり離れたりできるわけだ。

ViewController

Scene.sksをロードし、ARSKViewに表示。
ARSKViewにdelegateが設定されており、SceneがtouchされてScene.swiftで新しいanchorが追加されると、ViewControllerに書かれた以下のdelegateメソッドが呼ばれ、anchor位置に👾のSpriteを表示する。

以下のARSKViewのdelegateメソッドが新しいanchorが追加されたら呼ばれるもの
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { }
デリゲートに、新たに追加されたアンカーに対応するSpriteKitノードを提供するように要求します。
LabelNodeを作り、それをreturnしてるので、追加されたanchorの位置に表示している

任意の位置に表示させたい

Scene.swiftのtouchBegan()ではカメラを向けた方角の前方(設定では0.2m)に👾を表示させるべくARAnchorを追加している。
ARAnchorは4*4の行列で表されており、カメラを向けた位置の値(currentFrame.camera.transform)を別に用意した行列の変数(translation)と計算して変更している。
translationは行列の3列目のzの値に-0.2を設定し、カメラ位置の情報と演算することで前方0.2mのAnchor位置を設定している。
同じく以下のようにx,yの値を変更すれば、3次元空間上のAnchor位置を変更できる。なぜか横軸がy、縦軸がxっぽいのが納得いかないが。

AR開始時のカメラ(デバイス)の向きを基準に、以下のような意味になるようだ。

  • zは+が背後、-が前方
  • xは+が下、-が上方向
  • yは+が右手、-が左手

以下のように3列目のx,y,zを変更すれば、ある程度自由な位置に表示できる。

if let currentFrame = sceneView.session.currentFrame {
    var translation = matrix_identity_float4x4 //世界座標と向きを表す4*4の行列
    //zが奥行きの軸はいいとして、xが横軸、yが縦軸と思いきや、xが縦、yが横みたい?
    translation.columns.3.z = -0.2 //奥行き(前方0.2m)
    translation.columns.3.x = -1.0 //縦軸(上方1.0m)
    translation.columns.3.y = 0.5 //横軸(右手0.5m)
    //これは南を向いてデバイスを水平に構えた時の行列の値
    var transform = matrix_float4x4([0.0237875, -0.9977, 0.0634782, 0.0], [0.999125, 0.02154, -0.035859, 0.0], [0.0344092, 0.0642756, 0.997339, 0.0], [-0.000370748, -0.114079, -0.340914, 1.0])
    //南向き水平の値に対し、x,y,zの値を演算して変更している
    transform = simd_mul(transform, translation)

    //セッションに新しいARアンカーを追加
    let anchor = ARAnchor(transform: transform) //アンカーを作成
    sceneView.session.add(anchor: anchor) //sceneViewのsessionにアンカー追加
    //これによってViewControllerのdelegateが呼ばれる

}