- 投稿日:2020-06-04T23:29:48+09:00
Application Development Guide for Android and iOS
The complete guide on how to create a mobile application, from defining the project goal and technology stack to testing and deployment. Check it out!
- 投稿日:2020-06-04T23:04:36+09:00
How to attract more customers for your mobile app
How to promote a smartphone app, attract users, and earn an income? Check out the best mobile application marketing tools and strategies.
- 投稿日:2020-06-04T22:30:28+09:00
Top 15 Apps Like Instagram for IOS and Android you need to know about
Though Instagram continues to be the leading photo-sharing app, it has strong competitors. Check out 15 Instagram-like apps to see their success formula.
- 投稿日:2020-06-04T17:45:49+09:00
イテレータをSwift5で実装する
※この記事は「全デザインパターンをSwift5で実装する」https://qiita.com/satoru_pripara/items/3aa80dab8e80052796c6 の一部です。
The Iterator(イテレータ)
0. イテレータの意義
複数の要素の集合について、全要素に対し順に同様の処理を行うデザインパターンをイテレータという。
Swiftではfor-inループなどで使うことが出来る。
Array型, Set型, Dictionary型などあらかじめ用意されている型はもちろんだが、カスタムタイプもSequenceプロトコルに対応させることでfor-inループで回すことができる。
今回はキューを実装した後、それをSequenceプロトコルに対応させfor-inループで回せるようにする。
1. カスタムタイプ(キュー)の実装
キューとは、複数の要素を扱う型の一種でFirst-in Last-out(最初に追加した要素が最後に取り出される)が特徴の物を言う。
SwiftではUITableViewやUICollectionViewを使うときに、構成要素のセルをキューで管理するようになっているためよく見かけるかもしれない。
まず、自前でキューを実装する。キューの要素(ノード)を定義する必要がある。
Queue.swiftprivate final class Node<T> { //ジェネリクスTを用いて任意の肩を格納できる var key: T? //~中略~ init(_ value: T? = nil) { self.key = value } }キュー内では、複数のノードが連続して連なることになる。そのため、次のノードを参照する変数
next
を用意する。var next: Node?次にキュー本体を実装する。キューの先頭と最後のノードを参照する変数
head
、tail
を定義する。キューに一個も要素がない場合は先頭も最後もないことから、nilを許容するoptional型とする。Queue.swiftpublic final class Queue<T> { fileprivate var head: Node<T>? private var tail: Node<T>? public init(){} //後略新しい要素をキューの一番最後に追加するメソッド
enqueue(_:)
を定義する。キューの中に一つも要素がなく、最初の要素を追加する時は少し処理が違うため注意する。Queue.swiftpublic func enqueue(_ value: T) { //渡された値を元にノード(要素)を生成 let newNode = Node<T>(value) guard let _ = self.head else { //キューに要素が一つもない場合(一つ目の要素を追加する場合)の処理 self.head = newNode self.tail = self.head return } //キューにすでに要素がある場合(二つ目以降の要素を追加する場合)の処理 self.tail?.next = newNode self.tail = newNode }デキュー(先頭の要素をキューの中から取り出す)関数を定義する。
Queue.swiftpublic func dequeue() -> T? { //先頭にものがない(キューに要素が一つもない)場合はnilを返す guard let headItem = self.head?.key else { return nil } if let nextNode = self.head?.next { //二つ目の要素がある場合は、これを新しい先頭の要素とする self.head? = nextNode } else { //二つ目の要素がない場合は、デキューにより一つも //要素がなくなることに注意 self.head = nil self.tail = nil } //先頭の要素を返す return headItem }その他標準的な機能を実装する。
empty() -> Bool
: キューが空(要素数がゼロ)かどうかを判定する関数
peak() -> T
: キューの先頭の要素を返す(デキューはしない)Queue.swiftpublic func isEmpty() -> Bool { return head == nil } public func peek() -> T? { return self.head?.key }しかし、これだけではキューをfor-inループで回すことはできない。下記エラー文にあるように、キューを
Sequence
プロトコルに準拠させる必要がある。Queue.playgroundvar queue = Queue<Int>() queue.enqueue(1) queue.enqueue(2) for item in queue {//error: type 'Queue<Int>' does not conform to protocol 'Sequence' print(item) }2.
Sequence
プロトコルへの準拠
Sequence
プロトコルに準拠させるにはQueueIteratorという型をタイプエイリアスとして定義し、関数makeIterator()
でその型を返す必要がある。Queue.swiftextension Queue: Sequence { public typealias Iterator = QueueIterator<T> public func makeIterator() -> Queue<T>.Iterator { return QueueIterator(self) } }
QueueIterator
は、また別のプロトコルIterator
に準拠している必要がある。重要なのは
next()
関数である。この関数は、for-inループで回した時に、ある要素の処理が終わった後の次の要素を返す関数となっている。
「次の要素」が何か? という事を定義するには現在の要素が何か?という点を管理する必要がある。そのため
currentNode
という変数を用意し、next()
関数が呼ばれたらキュー上の次の要素を返す実装とする。Queue.swiftpublic struct QueueIterator<T>: IteratorProtocol { public typealias Element = T private let queue: Queue<T> private var currentNode: Node<T>? init(_ queue: Queue<T>) { self.queue = queue self.currentNode = queue.head } mutating public func next() -> QueueIterator<T>.Element? { guard let node = self.currentNode else {return nil} let nextKey = node.key self.currentNode = node.next return nextKey } }これでキューが
Sequence
プロトコルに準拠した。for-inループの中で使えるようになる。Queue.playgroundvar queue = Queue<Int>() queue.enqueue(1) queue.enqueue(2) for item in queue { print(item)//1 2 }https://github.com/Satoru-PriChan/QueueIteratorDemo
参考文献: https://www.amazon.com/Design-Patterns-Swift-implement-Improve-ebook/dp/B07MDD3FQJ
- 投稿日:2020-06-04T11:28:14+09:00
UIImageからGIFを生成する
個人開発アプリTweetFly
複数枚の画像をGIFに変換する機能がありまして、ちょっと調べてまとめました。下のGIFは
String
->UIImage
->GIF
に変換したサンプルです。
import UIKit import ImageIO import MobileCoreServices class GIFCreator { struct GIFFrame { var image: UIImage var duration: Float } static func create(with frames: [GIFFrame], resizeTo size: CGSize? = nil, filename: String = "temp.gif", completion: @escaping (URL?) -> Void) { DispatchQueue.global(qos: .userInteractive).async { // Resize images if needed let resizedFrames: [GIFFrame] if let size = size { let format = UIGraphicsImageRendererFormat() format.scale = 1 resizedFrames = frames.map { f in let resizedImage = UIGraphicsImageRenderer(size: size, format: format).image { _ in f.image.draw(in: CGRect(origin: .zero, size: size)) } return GIFFrame(image: resizedImage, duration: f.duration) } } else { resizedFrames = frames } let cacheUrl = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let url = cacheUrl.appendingPathComponent(filename) try? FileManager.default.removeItem(at: url) let cfURL = url as CFURL if let destination = CGImageDestinationCreateWithURL(cfURL, kUTTypeGIF, resizedFrames.count, nil) { let fileProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]] CGImageDestinationSetProperties(destination, fileProperties as CFDictionary?) for frame in resizedFrames { let gifProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: frame.duration]] CGImageDestinationAddImage(destination, frame.image.cgImage!, gifProperties as CFDictionary?) } DispatchQueue.main.async { CGImageDestinationFinalize(destination) ? completion(url) : completion(nil) } } else { DispatchQueue.main.async { completion(nil) } } } } static func create(with images: [UIImage], perFrameDuration: Float = 0.1, resizeTo size: CGSize? = nil, filename: String = "temp.gif", completion: @escaping (URL?) -> Void) { GIFCreator.create(with: images.map { GIFFrame(image: $0, duration: perFrameDuration)}, resizeTo: size, filename: filename, completion: completion) } }使い方は
1、各画像の表示時間が固定の場合は[UIImage]
をGIFCreator
に渡せばOKです。let images = ... GIFCreator.create(with: images) { url in guard let url = url, let data = try? Data(contentsOf: url) else { return } let gif = UIImage(data: data) }2、各画像の表示時間が個別で設定したい場合
let gifFrames: [GIFCreator.GIFFrame] = [ GIFCreator.GIFFrame(image: UIImage(), duration: 0.1), GIFCreator.GIFFrame(image: UIImage(), duration: 0.2), GIFCreator.GIFFrame(image: UIImage(), duration: 0.3) ] GIFCreator.create(with: gifFrames) { url in guard let url = url, let data = try? Data(contentsOf: url) else { return } let gif = UIImage(data: data) }
- 投稿日:2020-06-04T11:19:09+09:00
Unity IAPは公式APIドキュメントがないので課金処理は自前ネイティブ実装がいいかも
まとめ
- https://docs.unity3d.com/Manual/UnityIAP.html
- マニュアルはある。しかしAPIについての最新の説明は存在しないので自己責任でやるしかない。
- 自分でネイティブ連携するのが最強
現状
- https://forum.unity.com/threads/where-has-unity-iap-scripting-reference-gone.660454/
- Where Has Unity Iap Scripting Reference Gone?(Unity IAPのスクリプトリファレンスどこにやった?)
-https://forum.unity.com/threads/where-has-unity-iap-scripting-reference-gone.660454/#post-4543948
Understood on the concern. As we are moving more towards Package Manager, we've had to adjust our documentation work flow, and it's still a work in progress. It is something we are actively working on, apologies on the inconvenience.
JeffDUnity3D, May 15, 2019
(今作業中です(2019/05/15))つまり1年以上放置されてるわけで Unity 2018.3 以降は最新の情報を得る手段がありません。つまりLTS基準で考えると2018版から現在までドキュメントのないものを雰囲気で実装してるわけでAppleやらGoogleやらの方でPurchasing APIに変更やら機能追加やらが入ったときにちゃんと追従されるかも怪しい気がします。
- 投稿日:2020-06-04T10:32:23+09:00
iOSのダウングレード手順
iOSのダウングレード手順を説明します。
iTunes
がなくなったので、Finder
から行います。iOS FW(ips)を以下からダウンロードします。ダウングレードできるバージョンは、
Signed IPSWs
のみ有効です。次に、
Finder
を開いて、ダウングレードしたいデバイスを選択します。次に、macの場合は、
option(alt)
を押しながら、winの場合はshift
を押しながらアップデートを確認
ボタンをクリックします。すると、ファイルを選択できるようになるので、先ほどダウンロードした
.ips
を選択して、ダウングレードできます。
- 投稿日:2020-06-04T09:58:23+09:00
FirebaseCrashlyticsへのupload-symbols実行時にCould not fetch upload-symbolsのエラーが発生する
リリースしたアプリのクラッシュログ分析などでよく用いられるFirebaseCrashlytics。
FirebaseCrashlyticsを使用して、クラッシュログをもれなく収集するには、ライブラリ内のメソッドを使用するなどしてdsymファイルをFirebaseにアップロードする必要があるのですが、ここで表題のCould not fetch upload-symbols
のエラーが発生しました。同じエラーが発生した人の助けになればと思います。結論
結論から記述すると、Firebase側のコンソール設定が完了していなかったことが原因でした。
ただ、権限の関係で本番環境のコンソールが確認できなかった、なぜかFabricのライブラリ内にあるコマンドではアップロードができてしまった、などの複数の要因から原因に気づくのにも時間がかかりました。
コンソール画面の閲覧権限がないなど、同じような事象に出くわす方もいるかと考え、今回の記事作成に至りました。どんなことをしたか
FirebaseCrashlyticsについて
まずFirebaseCrashlyticsについて簡単に説明しておきます。
Firebaseが提供しているサービス。ライブラリの導入とアプリ起動時に数行の設定コードを記述することで、クラッシュログを収集しコンソール画面で管理ができます。公式ドキュメントに従ってセットアップする必要があります。
(日本語記事もありますが、記述が古い箇所があるので英語ドキュメントを参照するのがよいと思います)
本記事はこのセットアップ過程の中でFirebaseCrashlyticsのdSYMファイルアップロードの工程についての記事となります。CrashlyticsはもともとFabricによって提供されていたサービスで、FabricのFirebaseへの統合に伴い、CrashlyticsもFirebaseに移行されました。もともとFabricを使っていた場合、dSYMアップロードの際に若干方法が異なるので注意が必要です。
dSYMのアップロード方法
FirebaseCrashlyticsの公式ドキュメントに書いてある方法ですが、以下のコマンドをターミナルなどのコマンドラインから叩くことでアップロードが可能です。
/path/to/pods/directory/FirebaseCrashlytics/upload-symbols -gsp /path/to/GoogleService-Info.plist -p ios /path/to/dSYMsGoogleService-Info.plist・dSYMファイルのパスを指定して、ライブラリに用意されているコマンドを叩きます。
アップロードに成功するとSuccessfully uploaded Crashlytics symbols
が表示されます。Fabricを使っている場合には、この叩くべきコマンドが以下に変わります。
/path/to/pods/directory/Fabric/upload-symbols -a <FabricAPIKey> -p ios /path/to/dSYMsFabricの場合は、FabricのAPIKeyを指定してあげないといけないみたいです。
発生したエラーとその原因
FirebaseCrashlyticsライブラリのコマンドにて実行した際に、以下のエラーが発生しました。
Fetching upload-symbols settings... error: Could not fetch upload-symbols settings: An unknown error occurred fetching settings.調べてみると協業先のアカウントではFirebaseCrashlyticsの設定が行われていなかったことがわかりました。
これを設定後に再度試してみると、無事Successfully uploaded Crashlytics symbols
となり、アプリリリース後も無事クラッシュログを収集できました。本来、アカウント側の設定は一番最初に実施すべき工程として公式ドキュメントに記載されているので、完全なる凡ミスではあるのですが、開発用と本番用とでアカウントをわけていたり、コンソールが確認できないなどの事情があると、こう言ったケースは起こりうるのではないかなと思います。
当該のエラーが出た方がいたら、まずはアカウント側の設定を見直していただければよいかもしれません。補足
冒頭で「なぜかFabricのライブラリ内にあるコマンドではアップロードができてしまった」というふうに書きました。
これはアップロードに失敗した時に、試しにFabricで用意された下記のコマンドを叩いてみるとなぜかSuccessしてしまう、という事象でした。/path/to/pods/directory/Fabric/upload-symbols -gsp /path/to/GoogleService-Info.plist -p ios /path/to/dSYMsその後Firebase側のコンソール設定が未完了ということが判明したのですが、なぜこのFabricのコマンドではSuccessしてしまうのかが謎のままでした。
しかしこの記事を書く過程でみつけた記事でこんな記述にあたりました。
Fabric時代のアプリも通常のコマンドでアップロードして「Successfully uploaded Crashlytics symbols」と表示されるのですが、Firebase上では追記したコマンドでやらないといつまでたっても認識されていないようでした。
この記事の通常のコマンドとは、gspオプションでGoogleService-Info.plistのパス指定しているコマンドを指しているのですが、こちらの指定方法だとSuccessと表示されるものの、Firebase側には反映されていないようです。
もしかするとこちらのコマンドを叩いていることで、見かけ上成功しているが実際はアップロードされていなかったのかもしれません。
Fabricの場合のコマンドをググると、2パターンの指定方法がひっかかってしまいますが、APIキーを指定するコマンドを使わなくてはならないようですので、その点も要注意です。参考
Firebase公式 - Get deobfuscated crash reports with the Firebase Crashlytics SDK
Tomo.Log() - FirebaseでdSYMをアップロードする方法が変わってた
- 投稿日:2020-06-04T02:16:01+09:00
PlayFabでのプッシュ通知設定が難しすぎた
UnityのバックエンドをPlayFabに任せてみようと試みていたのですが、iOSのプッシュ通知設定が難しすぎたので備忘録です。
pemファイルのアップロードができない
PlayFabのプッシュ通知設定手順については、公式や紹介ブログがあるので詳細は割愛しますが、pemファイルをアップロードする段階で以下のようなエラーが発生してしまいます。
Invalid parameter: Reason: cannot be empty
pemの発行には問題無さそうで、まるでエラーの検討がつきません。
解決策
ぐぐってみると以下のフォーラムを発見。
https://community.playfab.com/questions/36895/instruction-on-using-admin-api-to-set-up-ios-push.html一言で言うと、「管理画面のUIはバグってて使い物にならないから、AdminAPIを使え」とのことでした。
そしてこのAdmin API、pemファイルの中身をいい感じに整形してリクエストパラメータに乗っける、という癖の強い仕様になっています。
リクエストパラメータの設定方法
以下フォーラム記事を翻訳したリクエストパラメータの設定手順です。
テキストエディタを使ってPEMファイルを開きます。2つの文字列のセクションがあり、1つは証明書、もう1つは秘密鍵です。
証明書のテキストを別の空のテキストファイルにコピーします。また、"-----BEGIN CERTIFICATE-----"と"-----END CERTIFICATE-----"のマーカーも必ずコピーしてください。そして、テキストエディタのreplaceメソッドを使って、すべての改行を「\n」に正確に変換して、1行の文字列にしてください。これはどのPlayFab公式ドキュメントにも記載されていません(えっ)
秘密鍵部分も同様に繰り返します。鍵のテキストの改行をすべて変換します。
PlayFab APIを呼び出すことができる環境(Postman、.NET Coreのコンソールアプリ、またはUnityのEditorスクリプト)で、SetupPushNotificationを呼び出します。
5 リクエストの "Key "プロパティに証明書の文字列を設定します。
6 Credentialプロパティに秘密鍵の文字列を設定します。
APIのインタフェースは公式を参照。
https://docs.microsoft.com/ja-jp/rest/api/playfab/admin/title-wide-data-management/setuppushnotification?view=playfab-rest分からなさすぎる
この人はどうやってこの解決策にたどり着けたのか謎です。すごい。。。
おかげで無事にiOSのプッシュ通知設定ができました。