20200228のSwiftに関する記事は6件です。

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だけ作成すれば後は、以下コマンドでmatchprefixのProfileを作成してくれるんですね。

方法

ex: devの場合

fastlane match development
# can be: appstore, adhoc, enterprise or development

以前、既存Profileをmatchに移行した経験から難しく考えすぎていました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftで漸近アニメーション

概要

「漸近って、数学のどっかで習ったなー」って感じのイメージだと思います。
ただそれは多分漸近線で、今回の内容とは似て非なるものだったりするので一旦全て忘れてください。

漸近は、だんだんと近づく的な意味です。
読み方は「ぜんきん」です。

これだけ聞いても、なんとなくアニメーションしてそうじゃないですか?

これが漸近アニメーションかー、ってなってもらえるように、コードと成果物で説明していきます。

成果物

今回は作り込んだアニメーションではなく、あくまでこういう風に書くとこういうことが実現できるよっていうことを伝えたいので、めちゃめちゃシンプルな成果物になっています。

Feb-28-2020 16-14-45.gif

ちょっとわかりにくいけど、どういうものかというと

  • 上へ向かう速度は一定
  • 目的地を決めて、円の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です。

フレームごとに、目的地へだんだん近づけることで、こういうアニメーションが完成するわけですね。

まとめ

アニメーションってどうやって動いてるんやろう、ってところが紐解いていけると、自分が実装できる幅も広がるので、漸近の知識は様々な表現に役立ちます!
何回漸近って言うねんって思われたはずなので、この辺でおさらば!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 CoreHaptics

2. エンジンをスタートする

エンジン(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. ハプティックパターンを再生する

CHHapticEnginemakePlayer(with:)メソッドにパターンを渡して、プレイヤー(CHHapticPatternPlayer)オブジェクトを生成します。

let player = try! engine.makePlayer(with: pattern)

CHHapticPatternPlayerstart(atTime)メソッドを呼んでハプティックを再生します。

try! player.start(atTime: CHHapticTimeImmediate)

ハプティックイベント(CHHapticEvent)

Core Hapticsとはカスタムハプティックパターンをつくって再生できるフレームワークです。そして、そのパターン(CHHapticPattern)は複数のハプティックイベント(CHHapticEvent)から構成され2

hapticpattern.png

ハプティックイベントは発生タイミング、イベントタイプ、長さ、そして複数のイベントパラメータ(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: TimeInterval

Continuousなイベントタイプの場合は、本プロパティに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.01.0で指定
  • hapticSharpness:ハプティックの鋭さを0.01.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.01.0で指定
  • audioPitch:音のピッチを-1.0(低い)〜1.0(高い)で指定
  • audioPan:音の位置(定位)を-1.0(左)〜1.0(右)で指定。デフォルトは0.0(中央)
  • audioBrightness:音の高周波成分を0.01.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.ParameterID

AHAP

AHAP (Apple Haptic and Audio Pattern)はハプティックパターンを定義するJSONライクなファイルフォーマットです。次のような構造でパターンを定義できます。

ahap.png

以下に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キーはdurationEventParametersキーはeventParametersプロパティに相当します。またEventParametersキー配下の配列に入る要素はCHHapticEventParameterに相当します。こうしてみれば、AHAPというフォーマットはハプティックパターン(CHHapticPattern)の実装をそのままJSONに落とし込んだだけであり、難しくはありません。

JSONフォーマットなので、AHAPは通常のテキストファイルとして閲覧・編集でき、Xcodeプロジェクトへの追加方法も他のリソースと同様です。

Core Hapticsは、このAHAPファイルからハプティックパターンを読み出して、再生することができます。

AHAPファイルからの再生

AHAPファイルを読み込んでハプティックパターンを再生する実装方法は非常にシンプルです。

エンジン(CHHapticEngine)を初期化し、

let engine = try! CHHapticEngine()

開始します。

try! engine.start()

あとはCHHapticEngineplayPattern(from:)メソッドを呼び、引数にAHAPファイルのURLを渡すだけです。

try! engine.playPattern(from: URL(fileURLWithPath: path))

以上でAHAPファイルからハプティックパターンを再生することができます。

サンプルコードのダウンロード

本記事は2019年9月に発売した書籍「iOS 13の新機能をざっくり把握する本」からの転載です。同書籍は現在は100円で販売しています。

またCore Hapticsは実際に手元で試してみないと文章だけでは伝わりにくいと思います。上記記事やWEBに転がっている情報で十分実装できるとは思いますが、サクッと試してみたい方はBOOTHにてサンプルも100円で販売しているので投げ銭がてらよろしければご利用ください。

https://shu223.booth.pm/items/1461791

カスタムパターンを生成するサンプルと、AHAPファイルを再生するサンプルが入っています。

IMG_0367.PNG


  1. Core Hapticsはオーディオによるフィードバックも扱うので、厳密にはTaptic Engineを制御するだけではありません。 

  2. 実際にはハプティックパターンを構成する要素としては他にCHHapticParameterCurveCHHapticDynamicParameterといったものがあります。 

  3. ただし、sustainedパラメータにfalse(0.0)をセットしておくと、Continuousなイベントタイプに対してduration0.0でも再生可能です。 

  4. Appleの"Haptic Sampler"というサンプルコードに付属しているInflate.ahapというファイルから抜粋したものです。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

参照先


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

参照先


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む