2018年12月24日月曜日

2018/12/24 有料アプリの手続き変更されたらしい

有料アプリの申請、更新手続きが変更されたようなので、和訳を書いておく。
更新された有料アプリケーションスケジュールを確認してください。
既存のアプリを更新し、新しいアプリ内購入を作成し、新しいアプリをApp Storeに送信するには、Legalロール(チームエージェント)を持つユーザーが、有料アプリケーションスケジュール(Apple Developer Programのスケジュール2)を確認して同意する必要があります。 [契約]、[税、および銀行]モジュールの[使用許諾契約]の順にクリックします。
この契約に同意するには、開発者Webサイトのアカウントで、最新バージョンのApple Developer Program使用許諾契約に同意している必要があります。
更新されたアップルデベロッパプログラムライセンス契約を確認する必要があります。
既存のアプリを更新して新しいアプリをApp Storeに送信するには、法的役割を持つユーザー(チームエージェント)が開発者Webサイトのアカウントで更新された契約を確認して同意する必要があります。

2018/12/21 韓国での法律改正

お上(Apple)からのメール。
以下は機械翻訳。
大韓民国で配布されているゲームに関する韓国の法律が変更されました。 あなたは現在韓国のApp Storeで1つ以上のゲームを配布しているので、Appleはあなたのゲームの名前とあなたのApple開発者アカウントに関連付けられたEメールアドレスを2019年1月1日から韓国ゲーム評価・管理委員会に提供する必要があります。
開発者とユーザーのデータを保護することは、私たちが真剣に取り組むことです。 この情報を共有したくない場合は、韓国でゲームを配布しないことを選択できます。 App Store Connectの各ゲームの「価格および配信状況」セクションで設定を調整するだけです。
要するに、韓国の法律が変わったことにより、韓国向けにゲームを提供するならアカウントに登録したメールアドレスを韓国の管理委員会に教えなきゃいけなくなるよ。嫌なら設定変更で韓国に配布しないことを選択できるよ。
というわけだ。

価格および配信状況の、「配信可否/選択されたすべてのテリトリ」を編集して、韓国だけチェックを外せばいいのだ。
めんどくさいからそのままにするつもりだけど。

2018年12月3日月曜日

変なエラー/Unknown selected data source for Port スピーカー (type: Speaker)

[avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port スピーカー (type: Speaker)

「スピーカーに対して、不明のデータソースが選ばれた」ってことか。

iOS12、Swift4.2、Xcode10になってから、上記のメッセージがずらずらと出るようになった。何も操作してないのに0.何秒かおきに次々と出る、もしくは数回出て止まったり。
ただWarningみたいなもので、実行上問題は出ていない。

音を鳴らすアプリではあるのだが、まだ音に関しての操作もコードもないViewControllerを実行中に出るので、コードの変更があったというよりはXcodeの設定を変更しなけりゃいけないのかもしれない。

ネットで調べると、Swift4.2でAVAudioSessionの関係で変更があり、try文を記述してどうのこうのと書いてあるが、音の操作関係のコードがないところでいっぱい出てるので、直しようがない。そもそもAVAudioSessionのコード書いてないし。

データソースが不明ということで音源ファイルが原因っぽいが、使ってるのはそれぞれmp3、m4r、m4aのフォーマットの音源なのだが、Xcodeの設定をどこかでいじらねばいけないのか?

原因はAdMob

広告にGoogleのAdMobを表示させてるんだけど、それが悪さしていたようで、AdMob表示させないようにしたらメッセージが出なくなった。

Google Mobile Ads SDK Developers掲示板でもその件が話されている。いつの投稿か日付がわかんないんだけど、最新の日付が2018/9/18頃らしいので、まあその少し前くらいだろう。
AdMob内でAVAudioSessionを使用してて、それの改修が遅れてるんだろう。
ちなみに現時点で最新のAdMobSDK 7.36.0(2018/9/10リリースらしい)にしてみたけどダメだった。
うざいメッセージだけど、実用上問題ないし、俺が悪いわけじゃないからいいや。さっさと直してほしいけどね。

Googleって高い給料もらってるすごいエンジニアばかりだと思うんだけど、こういうのを直すのがすごい遅い。直されないことも多いし。変な入社試験で人を選んでるせいでエンジニアが足りないの?

2018年11月15日木曜日

タッチ座標の色情報を得る

やりたいこと

UIImageViewをタッチしたピクセルの色情報とアルファ値を得る。
たとえば赤いピクセルをタッチしたら
r:255.0 g:0.0 b:0.0 a:255.0
などと出るように。

やり方

タッチ位置に相当するImageViewに貼られたImageのアドレスを参照し、色情報を得る。
ImageViewに表示したImageはたいてい縮小か拡大されてるので、画面のタッチ位置からImage上の位置を計算する。

Image上の位置から画像データのメモリ上のアドレスを計算する。
アドレス計算時は1ピクセルを何バイトで扱っているのかの考慮が必要。(4バイトだった)
Retinaディスプレイの場合、縦横ともデータが2.0倍とかなるので(最近は3.0倍もあるんだっけ?)それも考慮に入れてアドレス計算要。
(注:Retinaで2.0のscaleを得て計算したらかえってうまくいかなかったので、以下のコードでは1.0を代入してある。おそらく画面比率の計算とかを二重にやっちゃってるんじゃないかと思うんだけど、よくわからねぇ…(^^;)ゞ
画面のタッチ位置とオリジナルImage上の位置の変換をしなければ、Retina解像度を考慮に入れたやり方でうまくいくんじゃないかと思う…)

サイトを参考にUIImageを拡張して機能を付けた。

画像の本当の大きさとImageView上に貼られた画像の比率を計算する過程でAVFoundationをimport要。

注意点

CGImageは必ずしもRGBAの順番で並んでいるわけじゃないそうな。
CGImageのCGImageAlphaInfo.rawValueでどういう並びなのか調べられる
以下のコードではRGBAの並びとして書いている。

コード

Swift4.1
import UIKit
import AVFoundation

let pixelDataBytesSize = 4; //1ピクセルのバイト数

extension UIImage {
func getColor(pos: CGPoint) -> (color:UIColor, r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat) {
let imageData = self.cgImage?.dataProvider?.data //画像データ
let data:UnsafePointer = CFDataGetBytePtr(imageData) //画像データのポインタ(配列扱い)を得るらしい
let scale:CGFloat = 1.0 //UIScreen.main.scale //画面のスケール(Retinaなら多分2.0〜3.0)を得る(コメントアウト中)
//任意のピクセルアドレスを計算
let address:Int = ((Int(self.size.width) * Int(pos.y * scale)) + Int(pos.x * scale)) * pixelDataBytesSize
//RGBαの各値を得る
let r = CGFloat(data[address])
let g = CGFloat(data[address + 1])
let b = CGFloat(data[address + 2])
let a = CGFloat(data[address + 3])
return (UIColor(red: r / 255, green: g / 255, blue: b / 255, alpha: a / 255), r, g, b, a)
}
}

class ViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!
@IBOutlet weak var myImageView: UIImageView!

override func viewDidLoad() {
super.viewDidLoad()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//imageView上の実表示フレーム
let presentedFrame = AVMakeRect(aspectRatio: (myImageView.image?.size)!, insideRect: myImageView.bounds)
let marginX = presentedFrame.origin.x //上部のマージン
let marginY = presentedFrame.origin.y //左のマージン
if let touch = touches.first as UITouch? {
let loc = touch.location(in: myImageView) //タッチ座標
let image = myImageView.image
//画面上のタップ位置とimage上のタップ位置の変換
let tapX = (image?.size.width)! / presentedFrame.size.width * (loc.x - marginX)
let tapY = (image?.size.height)! / presentedFrame.size.height * (loc.y - marginY)
let color = image?.getColor(pos: CGPoint(x: tapX, y: tapY))
myLabel.textColor = color?.color
myLabel.text = "r:\(color!.r.description) g:\(color!.g.description) b:\(color!.b.description) a:\(color!.a.description)"
}
}
}

参考サイト

ImageViewに表示されたImageの、表示部分だけのframeを求める

ただし、AspectFitで表示された場合のみ有効。

ImageViewにImageを表示する際、contentModeの設定次第でImageを拡大/縮小して収めてくれるわけだが、Imageが実際に表示されている部分のframeサイズを得たいことがある。
そんな時に使うのが以下の関数。
ただしImageの縦横比を保ったまま拡大/縮小する .scaleAspectFit に設定された場合のみ。(他の設定にしても同じ結果しか得られなかった)
以下はすでにImageViewに表示中のImageについてframeサイズを得たが、表示前のImageでも大丈夫だろう。

Swift4.1
let presentedFrame = AVMakeRect(aspectRatio: (myImageView.image?.size)!, insideRect: myImageView.bounds)
print("表示サイズ \(presentedFrame)")

あらかじめAVFoundationをimportして使う。

ずっとわからず、比率を自前で計算してやってたんだけど、誤差が出たりとってもめんどうだった。
他のcontentModeでも使える関数ないかしら?

参考サイト:AS blind side/UIImagwViewにAspect Fitで配置されたUIImageの位置・サイズを求める

2018年10月31日水曜日

Xcode10.1アップデート

10月30日のアップルのスペシャルイベントで新しいMacBook Air、Mac mini、iPad Proなどが発表され、それらに対応したXcodeもリリースされた。

リリースノートによればSwift 4.2.1および、iOS12.1、WatchOS 5.1、tvOS 12.1、macOS MojaveのそれぞれのOSのSDKに対応ということだ。

2018年10月24日水曜日

次期macOSから全てのアプリに署名と公証が必要に?

情報ソース:AAPL. Ch.

iOSアプリはAppStore経由で流通するしか方法がないが、macOSのアプリは昔ながらに一般サイトなどで流通することができる。これは野良アプリと通称されるが、製作者を信頼するか自分で確認しない限り、マルウェアなどである可能性があるわけだ。
それを防止するため、macOS Mojaveではアップルによる事前チェック(公証サービス/Notary Service)を行い、アプリにマルウェアが含まれていないというお墨付きのチケットを発行できるようになった(んだって? 知らんかった)。

この検証結果を、悪意のあるアプリをブロックするGatekeeper機能と連携させ、実行時に「Appleによるチェックで悪質なソフトウェアは検出されませんでした。」とダイアログ表示させるのに使う。
(従来の野良アプリの場合、「このファイルは"Safariにより XXXX からダウンロードされました。」などと出る)
現在は希望したアプリだけだが、これを次期macOSから全てのアプリに適用させる考えだという。

んー、まあ安全であることに越したことはないけど、いちいち管理されるのもめんどいな。まさか安全性検証のために検査料を払えなんてことにならないといいが。

2018年9月28日金曜日

Mojaveにしたらビルドが超遅い


macOS 10.14 Mojaveに上げたところ、Xcode10.0で実機のビルドが途中で止まってしまうようになった。(シミュレータでも同じになるかはわからんが)
Building xx of yy tasksって出て、xxが25で止まったまま動かない。
しょうがないからビルドを止めようとSTOPボタンを押してもダメ。強制終了するしかなく、Xcodeの再起動にもまたえらい時間がかかる。
しょうがないのでそのまま数分間我慢してたら、いつもと同じようにビルドできるようになったけど、他のプロジェクトをビルドすると同じように途中で長々と止まる。

その後、Macを再起動したところ、このやたら時間のかかるビルドは解消されたっぽい。

Mojave自体、日本語入力がかなりもたつくなど、10.14.0の完成度は高くなさげ。
まあしばらくしたらアップデートされるだろうけど、もうちょっとこう、最初からちゃんとしててほしい。

2018年9月13日木曜日

ARのお勉強/Sprite版

ARSessionの開始手順

AR空間に表示するSpriteや3Dオブジェクトは別として、とりあえずARSessionだけ開始する方法

SKSceneを読み込み、SKSceneViewのsceneとして表示

@IBOutlet var sceneView: ARSKView! //StoryboardのSceneViewと接続されてる

if let scene = SKScene(fileNamed: "Scene") {
sceneView.presentScene(scene)
}

Scene.swiftへのアクセスは以下のようにすればできる。
let s = sceneView.scene asScene

AR空間を認識させる

バックカメラを使い、デバイスの位置、向き、AR空間の認識などを行う。開始時に行われ、数秒間待たされる。

let configuration = ARWorldTrackingConfiguration()

ARSession開始

ARWorldTrackingConfigurationで認識したAR設定に基づいてARSessionを開始する。
sceneView.session.run(configuration)

AR空間にオブジェクトを表示させる

ARAnchor

ARのオブジェクトを表示する位置を指定するのがARAnchor。
ARAnchorをAR空間に貼り付けた後、動かせるのか、動かせないのかはまだ不明。

行列でできている

ARAnchorの値は4*4の行列(matrix_identity_float4x4)で示される。
例)以下は変数に作られたばかりの行列の値
simd_float4x4([[1.0, 0.0, 0.0, 0.0)], [0.0, 1.0, 0.0, 0.0)], [0.0, 0.0, 1.0, 0.0)], [0.0, 0.0, 0.0, 1.0)]])

その行列の4個目(0オリジンなのでindexなら3)の要素のうちx, y, zがそれぞれAR空間の軸に対応しており、デバイスを中心にしたARAnchorの位置を1m = 1.0としてFloatで指定する。
  • xは0がデバイス位置、プラスが下↓、マイナスが上↑
  • yは0がデバイス位置、プラスが右方向→、マイナスが左方向←
  • zは0がデバイス位置、プラスが後方(液晶モニタ側)、マイナスが前方(カメラの先)

指定方法

//世界座標と向きを表す4*4の行列
var transform = matrix_identity_float4x4
//行列の3列目のz値に-0.2で前方0.2mを指定
transform.columns.3.x = 0
transform.columns.3.y = 0
transform.columns.3.z = -0.2

ARSKViewDelegate

AR空間に表示するものには、2DのSpriteと3Dのオブジェクトの2つあるが、今回は2DのSprite。
ARAnchorに実際にSpriteを貼るには以下のDelegateメソッドを利用する。

新規ARAnchor追加で呼ばれる

func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { }

ARKitのテンプレートで使われてるのがこちら。
新しいARAnchorが追加されると呼ばれ、そこに貼りたいSpriteをreturnしてやればいい。

ARAnchorにSKNodeが貼られると呼ばれる

func view(_ view: ARSKView, didAdd node: SKNode, for anchor: ARAnchor) { }

SpriteのActionを使う

view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? で返すSpriteNodeにActionを設定してやればいいのだが、直接そのSpriteNodeを返してもARAnchorに固定されているため、思ったような動きをしてくれない。
そこで空のSKNodeを作り(仮にreturnNodeとする)、それにaddChildeした上で、そのreturnNodeを返してやればいい。

let returnNode = SKNode()
中略
returnNode.addChild(spriteNode)
return returnNode

Spriteを消す時には、このダミーの親ノードも消さないとゴミが残っちゃうぞ。

2018年9月10日月曜日

Spriteの衝突検出

衝突判定検出用delegateの設定

class GameScene: SKScene, SKPhysicsContactDelegate { }

SKPhysicsWorldはSprite世界に関してのクラスで、GameSceneのdidMove()などで以下のように設定しておく。
self.physicsWorld.contactDelegate = self

これによって衝突時に didBegin()メソッド が呼ばれるようになる。

エッジベースの物理体とは

ゲーム中の障害物など、重力の影響を受けないスプライト。
縁(エッジ)しか使われないということか?

GameSceneの場合、範囲として自分のframeを与えてやればいい。
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)

ほかのSpriteは中心位置をoriginにした矩形になっているため、以下のようにwidthとheightの1/2だけ左上にずらしたCGRectで指定する。ちとめんどい。
self.label?.physicsBody = SKPhysicsBody(edgeLoopFrom: 
                  CGRect(x: -((self.label?.frame.size.width)! / 2),
                         y: -((self.label?.frame.size.height)! / 2),
                     width: self.frame.size.width,
                    height: self.frame.size.height))

ボリュームベースの物理体とは

ゲーム中のキャラクターなど、重力の影響を与えるスプライト。

categoryBitMaskの設定

操作する自分のキャラなのか、敵のキャラなのか、壁などの障害物なのかなどをざっくりカテゴライズすることで、衝突の影響を受けるのか、衝突時に didBegin()メソッドでチェックをするのかなどを細かに設定する。
32bitの任意のビットのみを1にすることで設定する。
以下のようにあらかじめカテゴライズ用定数を用意しておくと便利。
let rocketCate:UInt32 = 0x1 << 0
let labelCate:UInt32  = 0x1 << 1
let frameCate:UInt32  = 0x1 << 2

self.physicsBody?.categoryBitMask = frameCate
self.label?.physicsBody?.categoryBitMask = labelCate
sprite.physicsBody?.categoryBitMask = rocketCate

collisionBitMaskの設定

spriteのカテゴリーどうしの衝突(collision)の有無を決める。
これも32bitのプロパティで、categoryBitMaskの値を使って指定し、ビットが1になっているカテゴリーと衝突する。
デフォルトではすべてのカテゴリーどうしが衝突することになり、0に設定するとすり抜ける事になる。
2カテゴリー以上指定する場合は、カテゴリーどうしをor(|)でつないで指定する。
sprite.physicsBody?.collisionBitMask = frameCate | labelCate

contactTestBitMaskの設定

collisionBitMaskの設定だけでは、衝突して跳ね返ったりするものの、その検知がされないので、delegateメソッドのdidBegin()で検知する相手のカテゴリーを指定する。
sprite.physicsBody?.contactTestBitMask = labelCate

応用

collisionBitMaskを設定せず、contactTestBitMaskだけ設定すれば、すり抜けるけど衝突は検知するなんてこともできるわけだ。

衝突時のdelegateメソッド

SKPhysicsContactDelegateのメソッド。
引数のcontactで、どのspriteどうしが衝突したのか検出できる。
衝撃の値と衝突した位置も。
これを使って敵キャラを消したりするわけですな。

func didBegin(_ contact: SKPhysicsContact) {
    let bodyA = contact.bodyA
    let bodyB = contact.bodyB
    print("bodyA = \(String(describing: bodyA.node?.name)), bodyB = \(String(describing: bodyB.node?.name))")
    print("衝撃値 = \(contact.collisionImpulse)")
    print("衝突位置 = \(contact.contactPoint)")
}

撃ち落としたら消す

敵機を発射したミサイルが当たったら撃ち落とす場合は、最後に以下のようなコードを書いておく。あらかじめ敵機のnodeにenemyとでも名前をつけておく。

    if bodyA.node?.name == "enemy" || bodyB.node?.name == "enemy" {
        bodyA.node?.parent?.removeFromParent()
        bodyB.node?.parent?.removeFromParent()
    }

bodyAとbodyBのどっちが敵機、ミサイルになるのかは不定なので、どちらかが敵機だったら双方をremoveするようにする。
爆発の表現(particle)は未実装だが、remove処理の直前に処理を書けばいいだろう。

2018年8月11日土曜日

Found an unexpected Mach-O header code: 0x72613c21

問題

Xcode9の画面。
以前のバージョンでも出てた模様。
正常に動いていたアプリをAppStoreに提出すべくArchiveをし、Validate...しようとしたら
Found an unexpected Mach-O header code: 0x72613c21
という妙ちきりんなメッセージが出てニッチもサッチも行かなくなった。

意味は

訳すと「予期しない Mach-O ヘッダーのコードを発見した」である。
Mach-O(マーク・オー)はmacOSの前身であるNESTSTEP由来のコンパイラが生成するオブジェクトファイル/実行ファイルのファイルフォーマットだそうだ。(カーネルの名前がMachだからだな)
さらにMachHeaderはCPUの種類、ファイルの種類などの情報が書かれたヘッダのようだ。
要するに想定外の 0x72613c21 という情報がヘッダに書かれてるということだろう。

メッセージで検索すると 0x72613c21 の部分も含めて全く同じものが出ているようだ。人によって数値が変わるわけじゃないのだな。

どうやらこれはFrameworkをインストールした際の設定などがうまくいってないっぽい。
自分の場合TwitterKitと、画像処理のためのオープンソースライブラリのOpenCVをインストールしているので、それが関係していそう。

対処

Logファイルを見ても、エラーメッセージの後にDVTFilePathがどうのというのがいくつも出ているけど、さっぱりわからなかった。

./Users/YourFile/Library/Developer/Xcode/DerivedData の削除をしてもダメ

XcodeでProductのClean、Clean Build Folder...をしてもダメ。

解決した

TwitterKitの公式サイトに書かれていたインストール方法が、以下のようだった。
  • TwitterKitをEmbedded Binariesに
  • TwitterKitとTwitterCoreをLinked Frameworks and Librariesに
TwitterKitが2つ出てきておかしいとは思いつつ、Embedded 〜 に追加すると自動的にLinked 〜 にも追加されるんで、下の方ではTwitterCoreしか追加してなかったのだが、それが良くなかったようだ。


結果的にEmbedded 〜 の方を削除し、Linked 〜 の方にだけあらためてTwitterKitを追加したところ、無事Validate...が通った。
Embedded 〜 に残しておいても通ったけど、Linked 〜 にTwitterKitが2つ重複して入るため、気持ち悪いから消した。

原因

こちら(stack overflow)の回答を見たところ、以下のような記述があった(英語で)。
Mach-O型のFrameworkが「Static(静的)ライブラリ」である場合、それをEmbed(埋め込み)フレームワークに入れるべきではありません。 Mach-Oタイプが「Dynamic(動的)ライブラリ」の場合は、Embedフレームワークに配置する必要があります。
TwitterKitがStatic LibraryなのかDynamic Libraryなのかよくわからんのだけど、結果的にEmbedded Binaries に入れず、Linked Frameworks 〜 に入れるべきだったのだな。

とにかく解決したようなので助かった。こんなん、ネットがなかったら絶対わからんかったもん。
今後やはりEmbedded 〜 にも入れないといけない不具合が出るかもしれないけど、そしたらまた追記/訂正します。

2018年8月10日金曜日

Twitter利用の新アプリ作成時、アカウント作成要に

TwitterのApplication Managementページに載ってたのを翻訳しとく。

2018年7月現在、新しいアプリを作成する前に、Twitterデベロッパーアカウントを申請して承認を受ける必要があります。 承認されると、developer.twitter.comから新しいアプリを作成することができます。
近い将来、apps.twitter.comで既存のアプリを引き続き管理できます。 しかし、すぐにこのサイトを廃止し、developer.twitter.comの開発者ポータルにすべての開発ツール、APIアクセス、およびアプリケーション管理を統合します。 このサイトをリタイアすると、そのポータルを介して既存のアプリケーションにアクセスして管理することができます。
既存のアプリはいいけど、次からはデベロッパーアカウントを作らないといけないようだ。めんどくせえ。
詳しくは新規アプリ作った時に追記する。

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が呼ばれる

}

2018年6月9日土曜日

点線の描画

Draw図形を描くときに点線で描く方法があるんだね。意外と簡単だった。

UIBezierPathの場合

UIViewのdraw( )の中で、以下のように書く。

//MARK:丸
let oval = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 200))
UIColor.black.setFill() //stroke色
oval.lineWidth = 線幅
oval.setLineDash([10.0, 5.0, 5.0, 5.0], count: 4, phase: 0.0)

oval.stroke() //枠線描画

setLineDashがそれで、引数はUnsafePointerがどーとかわかりにくいんだけど、要は以下の3つだ。

  1. 点線の長さ、余白の長さの順に配列で指定
  2. 配列の要素数
  3. 配列の要素の使用開始位置
oval.setLineDash(UnsafePointer:<CGFloat>?, count: Int, phase: CGFloat)

CAShapeLayerの場合

こっちも似たようなもんだろう。

let layer = CALayer()
     
let lineDashPatterns: [[NSNumber]?]  = [nil, [2,3], [10, 5, 5, 5]]
   
for (index, lineDashPattern) in lineDashPatterns.enumerated() {
   
    let shapeLayer = CAShapeLayer()
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.lineWidth = 5
    shapeLayer.lineDashPattern = lineDashPattern
   
    let path = CGMutablePath()
    let y = CGFloat(index * 50)
    path.addLines(between: [CGPoint(x: 0, y: y),
                            CGPoint(x: 640, y: y)])
   
    shapeLayer.path = path
   
    layer.addSublayer(shapeLayer)
}

参考サイト

2018年6月5日火曜日

WWDC2018

ゆんべ美國でおこなわれたWWDCの開発関係の自分用まとめ。


  • iOS 12発表
    • リリースは秋
    • インストール対象はiOS11が動くデバイス
    • パフォーマンスが向上
    • Pixarと共同でAR関係の新ファイルフォーマット USDZ(ユーエスディーズィー) を開発
    • Apple純正AR物差しアプリ 「Measure」発表
    • ARKit 2発表
      • 顔認識やレンダリングその他を改良、最大4人のマルチユーザをサポート
    • その他もろもろ
  • macOS Mojave(モハーベ)発表
    • リリースは秋
    • iOSのUIKitをmacOSに導入
      • Appleからニュース、株式、音声メモ、ホームの各iOSアプリをMac用にリリース
      • 2019年にデベロッパにも提供され、iOSアプリをmacOSに移植可能に
      • iOSとmacOSの統合は否定
OSの改良でパフォーマンス向上してくれるのは歓迎。
でもまたいろいろいじらなきゃいけない点が出てくるだろーなー。仮により簡単に作れるようになったとしても、概念や扱い方が大きく違ったりすると直すのがめんどい。こちとら新しいアプリ作りたいんじゃ。過去のアプリのサポートばっかりやってられるかい!

ARKit1もまだよく使ってない。こないだiPhone買ったばかりだし。
Measureって、日本のAppStoreに同名のアプリ(個人開発)があるんだけど、いいのか? まさかそのアプリを買収するわけじゃないよね?

UIKitがMacでも使えるようになるのはいいね。扱いが統一されるとやりやすい。移植可能になるというより、移植が楽になるってこっちゃな。今までのmacOSのUIを司ってたAPIを置き換えちゃうわけじゃないんでしょ?
iOSはUIKitだけにあらずなので、そのほかのAPIというかFrameworkの互換性ってのはどんなもんなのかね?