- 投稿日:2020-02-28T19:35:21+09:00
fastlane matchに新しいProvisioning Profileを追加する
新しく環境を追加したとき、fastlane matchにProvisioning Profileを追加する方法
今回、APIの向き先が増え、Build ConfigurationでBundleIDを追加したので必要になりました。
余談
この辺を見て…
https://docs.fastlane.tools/actions/match/Import To import and encrypt a certificate (.cer) and the private key (.p12) into the match repo run: fastlane match importフムフム、Provisioning Profileの場合もimportするのかな? と思っていたら...
AppIDだけ作成すれば後は、以下コマンドで
match
prefixのProfileを作成してくれるんですね。方法
ex: devの場合
fastlane match development # can be: appstore, adhoc, enterprise or development以前、既存Profileをmatchに移行した経験から難しく考えすぎていました。
- 投稿日:2020-02-28T18:17:28+09:00
Swiftで漸近アニメーション
概要
「漸近って、数学のどっかで習ったなー」って感じのイメージだと思います。
ただそれは多分漸近線で、今回の内容とは似て非なるものだったりするので一旦全て忘れてください。漸近は、だんだんと近づく的な意味です。
読み方は「ぜんきん」です。これだけ聞いても、なんとなくアニメーションしてそうじゃないですか?
これが漸近アニメーションかー、ってなってもらえるように、コードと成果物で説明していきます。
成果物
今回は作り込んだアニメーションではなく、あくまでこういう風に書くとこういうことが実現できるよっていうことを伝えたいので、めちゃめちゃシンプルな成果物になっています。
ちょっとわかりにくいけど、どういうものかというと
- 上へ向かう速度は一定
- 目的地を決めて、円のx座標を目的地へだんだん近づける
- 目的地に着いたら、新たな目的地を決める
って感じ。
細かい話はこれから説明していきます。実装
まずは、目的地用のstruct
struct AsymptoticDestination { var x : CGFloat = 0.0 // 目的地のx座標 var time : Int = 0 // 目的地まで何フレームで移動するか var count : Int = 0 // 移動しだして何フレーム目か // 次の目的地を決める mutating func calculateDestination() { self.x = CGFloat(arc4random_uniform(UInt32(UIScreen.main.bounds.width))) self.time = Int(arc4random_uniform(90)) + 10 self.count = 0 } // countを毎フレーム増やしていき、timeに到達したら次の目的地へ mutating func countUp() { self.count += 1 if self.count == self.time { self.calculateDestination() } } }ループを回して状態管理するので、ループ部分
と、円レイヤー周りfinal class AsymptoticView: BaseView { private var displayLink: CADisplayLink? private var destination: AsymptoticDestination = .init() private var currentPoint: CGPoint = .zero private var circle = CALayer() } // MARK: - loop extension AsymptoticView { // ループ設定 func setupLoop() { self.displayLink = CADisplayLink(target: self, selector: #selector(self.update)) if #available(iOS 10.0, *) { self.displayLink?.preferredFramesPerSecond = 60 } else { self.displayLink?.frameInterval = 1 } self.displayLink?.add(to: .current, forMode: .common) self.displayLink?.isPaused = true // 設定時は止めておく } // ループ開始 func startTick() { if self.displayLink == nil { self.setupLoop() } self.displayLink?.isPaused = false } // ループ停止 func stopTick() { self.displayLink?.isPaused = true self.displayLink?.invalidate() } // 毎フレーム呼び出される @objc private func update(displayLink: CADisplayLink) { self.updateCircle() } } // MARK: - circle extension AsymptoticView { func setupCircle() { // 初期位置決め self.setupCurrentPoint() // 円を描く self.circle = CAShapeLayer() self.circle.bounds = CGRect(x: 0, y: 0, width: 44.0, height: 44.0) self.circle.position = self.currentPoint self.circle.backgroundColor = UIColor.red.cgColor self.circle.cornerRadius = 22.0 self.layer.addSublayer(self.circle) } private func setupCurrentPoint() { self.currentPoint = CGPoint(x: self.bounds.size.width / 2, y: self.bounds.size.height) self.destination.calculateDestination() } private func updateCircle() { self.currentPoint.x += (self.destination.x - self.currentPoint.x) / CGFloat(self.destination.time) // *1 self.currentPoint.y -= 1.0 // y座標方向のスピード self.circle.position = self.currentPoint self.destination.countUp() } }*1と書いた部分が、今回のポイントです。
漸近の「だんだん近づける」という部分を実装したところですね!今の位置と目的地のx座標の差分を算出し、
その差分を何フレームで埋めるかっていう値で割ると、
1フレーム当たりの移動量が計算できます。
それが*1です。フレームごとに、目的地へだんだん近づけることで、こういうアニメーションが完成するわけですね。
まとめ
アニメーションってどうやって動いてるんやろう、ってところが紐解いていけると、自分が実装できる幅も広がるので、漸近の知識は様々な表現に役立ちます!
何回漸近って言うねんって思われたはずなので、この辺でおさらば!
- 投稿日:2020-02-28T17:58:17+09:00
Multiple targets match implicit dependency for linker flags エラーの解決法
FirebaseでGoogleSignInを導入したらエラーが発生。
share extensionを使った時も同じようなエラーが出た。
解決法
podfileを修正
以下を追加
pod 'GTMSessionFetcher'解決後
# Uncomment the next line to define a global platform for your project platform :ios, '10.0' use_frameworks! def google_utilites pod 'GoogleUtilities/AppDelegateSwizzler' pod 'GoogleUtilities/Environment' pod 'GoogleUtilities/ISASwizzler' pod 'GoogleUtilities/Logger' pod 'GoogleUtilities/MethodSwizzler' pod 'GoogleUtilities/NSData+zlib' pod 'GoogleUtilities/Network' pod 'GoogleUtilities/Reachability' pod 'GoogleUtilities/UserDefaults' pod 'GTMSessionFetcher' end target 'myApp' do google_utilites pod 'Firebase/Auth' pod 'Firebase/Database' pod 'Firebase/Storage' pod 'Firebase/Analytics' pod 'GoogleSignIn' end target 'shareExtension' do google_utilites pod 'Firebase/Auth' pod 'Firebase/Database' pod 'Firebase/Storage' end
- 投稿日:2020-02-28T10:26:46+09:00
Core Haptics - カスタムハプティックパターンの作成と再生
Core Hapticsは、iOS 13で新たに追加された「ハプティック(触覚)パターンを作成し、再生する」ためのフレームワークです。ついにTaptic Engineを開発者が制御するためのAPIが公開されたというわけです。1
従来手法(UIFeedbackGenerator)との違い
Taptic Engineが初めて搭載されたのはiPhone 6sで、ハプティックパターンの再生自体は、iOS 10で追加された
UIFeedbackGenerator
により以前から可能でした。
UIFeedbackGenerator
は抽象クラスで、UIImpactFeedbackGenerator
,UINotificationFeedbackGenerator
,UISelectionFeedbackGenerator
の3種類の具象クラスがあります。それぞれの実装例を以下に示します。
UIImpactFeedbackGenerator
let impactFeedbacker = UIImpactFeedbackGenerator(style: .heavy) impactFeedbacker.prepare() impactFeedbacker.impactOccurred()
UINotificationFeedbackGenerator
let notificationFeedbacker = UINotificationFeedbackGenerator() notificationFeedbacker.notificationOccurred(.success)
UISelectionFeedbackGenerator
let selectionFeedbacker = UISelectionFeedbackGenerator() @IBAction func sliderChanged(_ sender: UISlider) { selectionFeedbacker.selectionChanged() }上記のコード例からもわかる通り、いずれの場合もあらかじめ決められたタイプのハプティックパターンを再生するというものでした。
Core Hapticsの場合は、このハプティックパターンをカスタマイズできる点が従来手法と大きく違う点です。
Core Hapticsの実装
まずはCore Hapticsの全体感を掴むため、基本的な実装の流れを見てみましょう。
1. インポート
CoreHapticsをインポートします。
import CoreHaptics2. エンジンをスタートする
エンジン(
CHHapticEngine
)を初期化し、let engine = try! CHHapticEngine()
start()
メソッドを呼んでスタートします。try! engine.start()3. ハプティックパターンを生成する
ハプティックイベント(
CHHapticEvent
)を生成し(詳細は後述)、let audioEvent = CHHapticEvent(eventType: .audioContinuous, parameters: [ CHHapticEventParameter(parameterID: .audioPitch, value: -0.15), CHHapticEventParameter(parameterID: .audioVolume, value: volume), CHHapticEventParameter(parameterID: .decayTime, value: decay), CHHapticEventParameter(parameterID: .sustained, value: 0) ], relativeTime: 0) let hapticEvent = CHHapticEvent(eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness), CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity) ], relativeTime: 0)複数のハプティックイベントを組み合わせてパターン(
CHHapticPattern
)を生成します。let pattern = try! CHHapticPattern(events: [audioEvent, hapticEvent], parameters: [])4. ハプティックパターンを再生する
CHHapticEngine
のmakePlayer(with:)
メソッドにパターンを渡して、プレイヤー(CHHapticPatternPlayer
)オブジェクトを生成します。let player = try! engine.makePlayer(with: pattern)
CHHapticPatternPlayer
のstart(atTime)
メソッドを呼んでハプティックを再生します。try! player.start(atTime: CHHapticTimeImmediate)ハプティックイベント(CHHapticEvent)
Core Hapticsとはカスタムハプティックパターンをつくって再生できるフレームワークです。そして、そのパターン(
CHHapticPattern
)は複数のハプティックイベント(CHHapticEvent
)から構成され2、ハプティックイベントは発生タイミング、イベントタイプ、長さ、そして複数のイベントパラメータ(
CHHapticEventParameter
)より規定されます。つまり、カスタムなハプティックパターンをつくる重要な構成要素である
CHHapticEvent
を規定する各種プロパティとCHHapticEventParameter
を理解することがCore Hapticsを使いこなす鍵となります。というわけで、以下で
CHHapticEvent
を規定する各種要素について順番に解説していきます。ハプティックイベントタイプ(CHHapticEvent.EventType)
CHHapticEvent
は次のようなイニシャライザを持ち、第1引数にイベントタイプ(CHHapticEvent.EventType
)を渡せるようになっています。init(eventType type: CHHapticEvent.EventType, parameters eventParams: [CHHapticEventParameter], relativeTime time: TimeInterval)これはその名の通りハプティックイベントのタイプを決めるもので、次の4種類が定義されています。
static let audioContinuous: CHHapticEvent.EventType static let audioCustom: CHHapticEvent.EventType static let hapticTransient: CHHapticEvent.EventType static let hapticContinuous: CHHapticEvent.EventType
audio〜
はオーディオによるフィードバック、haptic〜
は触覚によるフィードバックです。hapticタイプの方は"Transient"と"Continuous"とがありますが、"Transient"とは「一時的な」という意味で、
hapticTransient
は短いインパルス的なハプティックを、hapticContinuous
は任意の長さを持ち、ループするハプティックを示します。CHHapticEventの発生タイミング(relativeTime)
上述した
CHHapticEvent
のイニシャライザは、第3引数にイベントを開始する時間(relativeTime
)を指定できるようになっています。名前に"relative"とある通り、絶対時間ではなく、ハプティックパターン内における相対時間(単位は秒)で指定します。
let hapticEvent = CHHapticEvent( eventType: type, parameters:params, relativeTime: 0.1) // 0.1秒後に開始CHHapticEventの長さ(duration)
CHHapticEvent
はハプティックイベントの長さを指定するduration
プロパティを持ちます。var duration: TimeIntervalContinuousなイベントタイプの場合は、本プロパティに
0.0
より大きい値をセットしておかないと再生時にクラッシュします。3最大値は30秒です。
イベントパラメータ(CHHapticEventParameter)
前述の
CHHapticEvent
のイニシャライザの第2引数に配列で渡すのが、イベントパラメータ(CHHapticEventParameter
)です。let audioEvent = CHHapticEvent( eventType: type, parameters: [param1, param2], // [CHHapticEventParameter] relativeTime: 0)イニシャライザは次のように定義されており、
init(parameterID: CHHapticEvent.ParameterID, value: Float)第1引数にパラメータの種類を示すID(
CHHapticEvent.ParameterID
)、第2引数にパラメータの値(Float
)を渡します。let pitch = CHHapticEventParameter(parameterID: .audioPitch, value: -0.15) let volume = CHHapticEventParameter(parameterID: .audioVolume, value: 0.5)
CHHapticEvent.ParameterID
には多くの種類があるので、ここでは主なものを紹介します。haptic用
イベントタイプが
hapticTransient
,hapticContinuous
なハプティックイベント専用のパラメータIDが次の2つです。static let hapticIntensity: CHHapticEvent.ParameterID static let hapticSharpness: CHHapticEvent.ParameterID
hapticIntensity
:ハプティックの強さを0.0
〜1.0
で指定hapticSharpness
:ハプティックの鋭さを0.0
〜1.0
で指定audio用
イベントタイプが
audioContinuous
,audioCustom
なハプティックイベント専用のパラメータIDが次の4つです。static let audioVolume: CHHapticEvent.ParameterID static let audioPitch: CHHapticEvent.ParameterID static let audioPan: CHHapticEvent.ParameterID static let audioBrightness: CHHapticEvent.ParameterID
audioVolume
:音量を0.0
〜1.0
で指定audioPitch
:音のピッチを-1.0
(低い)〜1.0
(高い)で指定audioPan
:音の位置(定位)を-1.0
(左)〜1.0
(右)で指定。デフォルトは0.0
(中央)audioBrightness
:音の高周波成分を0.0
〜1.0
で指定。デフォルトは1.0
(高周波成分を減らさない)共通
audio, haptic共通で"Continuous"なイベントタイプに使えるパラメータのIDとして、次のようなものがあります。それぞれハプティックイベントの波形を細かく調整するためのパラメータです。
static let attackTime: CHHapticEvent.ParameterID static let decayTime: CHHapticEvent.ParameterID static let releaseTime: CHHapticEvent.ParameterID static let sustained: CHHapticEvent.ParameterIDAHAP
AHAP (Apple Haptic and Audio Pattern)はハプティックパターンを定義するJSONライクなファイルフォーマットです。次のような構造でパターンを定義できます。
以下にAHAPファイルの例を示します。4
{ "Version": 1.0, "Metadata": { "Project" : "Haptic Sampler", "Created" : "5 June 2019", "Description" : "An effect that builds in sharpness and intensity." }, "Pattern": [ { "Event": { "Time": 0.0, "EventType": "HapticContinuous", "EventDuration": 1.7, "EventParameters": [ { "ParameterID": "HapticIntensity", "ParameterValue": 1.0 }, { "ParameterID": "HapticSharpness", "ParameterValue": 0.5 } ] } }, { "ParameterCurve": { "ParameterID": "HapticIntensityControl", "Time": 0.0, "ParameterCurveControlPoints": [ { "Time": 0, "ParameterValue": 0.0 }, { "Time": 1.1, "ParameterValue": 0.5 }, { "Time": 1.7, "ParameterValue": 0.0 } ] } }, ...(略) ] }この定義のうち
Event
キーはCore HapticsのCHHapticEvent
に相当し、その配下のTime
キーはrelativeTime
プロパティ、EventType
キーはeventType
プロパティ、EventDuration
キーはduration
、EventParameters
キーはeventParameters
プロパティに相当します。またEventParameters
キー配下の配列に入る要素はCHHapticEventParameter
に相当します。こうしてみれば、AHAPというフォーマットはハプティックパターン(CHHapticPattern
)の実装をそのままJSONに落とし込んだだけであり、難しくはありません。JSONフォーマットなので、AHAPは通常のテキストファイルとして閲覧・編集でき、Xcodeプロジェクトへの追加方法も他のリソースと同様です。
Core Hapticsは、このAHAPファイルからハプティックパターンを読み出して、再生することができます。
AHAPファイルからの再生
AHAPファイルを読み込んでハプティックパターンを再生する実装方法は非常にシンプルです。
エンジン(
CHHapticEngine
)を初期化し、let engine = try! CHHapticEngine()開始します。
try! engine.start()あとは
CHHapticEngine
のplayPattern(from:)
メソッドを呼び、引数にAHAPファイルのURL
を渡すだけです。try! engine.playPattern(from: URL(fileURLWithPath: path))以上でAHAPファイルからハプティックパターンを再生することができます。
サンプルコードのダウンロード
本記事は2019年9月に発売した書籍「iOS 13の新機能をざっくり把握する本」からの転載です。同書籍は現在は100円で販売しています。
「iOS 13の新機能をざっくり把握する本」の電子版をなんと!100円にしてみました。
— Shuichi Tsutsumi (@shu223) January 23, 2020
理由:
・iOS ○と題された本は時間経過で非常に売れにくくなる
・「100円なら買う」層はどれぐらいいるのかの実験
・本が売れたときの通知がくると単純に嬉しい
・(続)https://t.co/aRqpnHp6vHまたCore Hapticsは実際に手元で試してみないと文章だけでは伝わりにくいと思います。上記記事やWEBに転がっている情報で十分実装できるとは思いますが、サクッと試してみたい方はBOOTHにてサンプルも100円で販売しているので投げ銭がてらよろしければご利用ください。
https://shu223.booth.pm/items/1461791
カスタムパターンを生成するサンプルと、AHAPファイルを再生するサンプルが入っています。
Core Hapticsはオーディオによるフィードバックも扱うので、厳密にはTaptic Engineを制御するだけではありません。 ↩
実際にはハプティックパターンを構成する要素としては他に
CHHapticParameterCurve
やCHHapticDynamicParameter
といったものがあります。 ↩ただし、
sustained
パラメータにfalse(0.0
)をセットしておくと、Continuousなイベントタイプに対してduration
が0.0
でも再生可能です。 ↩Appleの"Haptic Sampler"というサンプルコードに付属している
Inflate.ahap
というファイルから抜粋したものです。 ↩
- 投稿日:2020-02-28T06:22:17+09:00
iOS13.3.1の実機でadmobバナーを表示できなかった件
やりたかったこと
iOS向けのアプリケーション作成に手を出しはじめたのですが、admobによる広告をつけようとしたときの件です。
公式のスタートガイド(https://developers.google.com/admob/ios/banner?hl=ja) に従い進めたところ、
シミュレータでのテスト広告表示まで問題なく確認できたが、実機(iPad mini 4)でのテストで躓いた部分を書きます。前提/背景
admobによる広告表示のため、Xcodeのプロジェクトを作成した後に、CocoaPodsを用いて
Google-Mobile-Ads-SDK
をインポートしました。公式ガイドの通りに実装をしてiPhoneSE, iPhone8のシミュレータでテスト広告の表示まで順調に確認できてました。
実機でのテストにおけるランタイムエラー
しかし、いざ実機でアプリケーションを動かそうとしたときに問題が発生しました。
ビルドは成功するのだが、実行時エラーでアプリケーションが落ちてしまう事象に遭遇しました。
- エラーメッセージは下記の通りです(一部)。
dyld: Library not loaded: @rpath/GoogleUtilities.framework/GoogleUtilities
Referenced from 〜以下略原因
iOS 13.3.1 以降はfree developerの作成したアプリでは、ダイナミックフレームワークを拒否するようで、実行時にエラーが起きるらしいです。
詳しく理解できていないのですが、Googleの提供するフレームワークはOSアップデートにより、開発用アカウントの作成したアプリでは動かないみたいです。対応
Podfileの下記部分を変更し、
pod update
を行ったところ無事実機での動作を確認できました。target 'xxx' do # Comment the next line if you don't want to use dynamic frameworks -use_frameworks! +use_modular_headers!記事作成時に気がついたんですが、思いっきり該当するコメントありますね...
# Comment the next line if you don't want to use dynamic frameworks
もしかしたら上記の変更ではなく、コメントアウトだけで動作するのかもしれないですね。開発者登録していないアカウント(下記参照先の原文におけるfree developer)で起こる事象のようなので、
きちんと年貢を納めていれば起こらないエラーなのでしょうね。多分事象が起こる対象が限定されることから、日本語記事があまり見つかりませんでした。
私のような駆け出しの方が躓くようなことがあった時に助けになれば幸いです。環境
- Xcode: Version 11.3.1
- iPad mini 4: システムバージョン 13.3.1
参照先
- https://stackoverflow.com/questions/60249693/application-crash-when-calling-method-firebaseapp-configure-in-a-real-device/60303927#60303927
- https://github.com/firebase/firebase-ios-sdk/issues/4723
- 投稿日:2020-02-28T06:22:17+09:00
admobの広告を実機で表示する際にエラーが起きたときの対処
やりたかったこと
iOS向けのアプリケーション作成に手を出しはじめたのですが、admobによる広告表示を実機で確認しようとしたとき、
下記のエラーに見舞われましたが、なんとか表示まで持っていけたという話です。公式のスタートガイド(https://developers.google.com/admob/ios/banner?hl=ja) に従い進めたところ、
シミュレータでのテスト広告表示まで問題なく確認できたが、実機(iPad mini 4)でのテストで躓いた部分を書きます。前提/背景
admobによる広告表示のため、Xcodeのプロジェクトを作成した後に、CocoaPodsを用いて
Google-Mobile-Ads-SDK
をインポートしました。公式ガイドの通りに実装をしてiPhoneSE, iPhone8のシミュレータでテスト広告の表示まで順調に確認できてました。
実機でのテストにおけるランタイムエラー
しかし、いざ実機でアプリケーションを動かそうとしたときに問題が発生しました。
ビルドは成功するのだが、実行時エラーでアプリケーションが落ちてしまう事象に遭遇しました。
- エラーメッセージは下記の通りです(一部)。
dyld: Library not loaded: @rpath/GoogleUtilities.framework/GoogleUtilities
Referenced from 〜以下略原因
iOS 13.3.1 以降はfree developerの作成したアプリでは、ダイナミックフレームワークを拒否するようで、実行時にエラーが起きるらしいです。
詳しく理解できていないのですが、Googleの提供するフレームワークはOSアップデートにより、開発用アカウントの作成したアプリでは動かないみたいです。対応
Podfileの下記部分を変更し、
pod update
を行ったところ無事実機での動作を確認できました。target 'xxx' do # Comment the next line if you don't want to use dynamic frameworks -use_frameworks! +use_modular_headers!記事作成時に気がついたんですが、思いっきり該当するコメントありますね...
# Comment the next line if you don't want to use dynamic frameworks
もしかしたら上記の変更ではなく、コメントアウトだけで動作するのかもしれないですね。開発者登録していないアカウント(下記参照先の原文におけるfree developer)で起こる事象のようなので、
きちんと年貢を納めていれば起こらないエラーなのでしょうね。多分事象が起こる対象が限定されることから、日本語記事があまり見つかりませんでした。
私のような駆け出しの方が躓くようなことがあった時に助けになれば幸いです。環境
- Xcode: Version 11.3.1
- iPad mini 4: システムバージョン 13.3.1
参照先
- https://stackoverflow.com/questions/60249693/application-crash-when-calling-method-firebaseapp-configure-in-a-real-device/60303927#60303927
- https://github.com/firebase/firebase-ios-sdk/issues/4723