ColorfulBoxでCLIのPHPのバージョンを変更する方法
ColorfulBoxというレンタルサーバーを使ってPHPのアプリケーションを公開してみました。
レンタルサーバーというものを人生で初めて触れているもので勝手がどうにも分からず、PHPのバージョン変更するだけでもだいぶ手こずってしまいました。
分かってしまえば簡単なことだったので自責の念を込めつつ共有します。
結論
- WebサーバーのPHPバージョン設定とCLIのPHPバージョンは別物
- レンタルサーバーの都合上CLIのPHP実体を変えることはできない
- PATHの順番を弄って自分のホームディレクトリに配置したPHPを利用するようにする
2種類のPHPバージョン
そもそもPHPのバージョンと一口にいっても以下の2種類を考慮する必要があるみたいです。
前者のPHPバージョンについてはColorfulBoxの管理画面であるcPanel上で変更できるようになっています。
CLIのPHPはというと以下のようにPHP 5.6.40がデフォルトで使われていて、このバージョンではcomposerやLaravelのコマンドで色々困ることが出てきます。
$ ll /usr/local/bin/ total 56 lrwxrwxrwx 1 root root 37 Dec 28 17:57 ea-php53 -> /opt/cpanel/ea-php53/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 17:57 ea-php54 -> /opt/cpanel/ea-php54/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 16:59 ea-php55 -> /opt/cpanel/ea-php55/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 16:59 ea-php56 -> /opt/cpanel/ea-php56/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 16:59 ea-php70 -> /opt/cpanel/ea-php70/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 17:57 ea-php71 -> /opt/cpanel/ea-php71/root/usr/bin/php lrwxrwxrwx 1 root root 37 Dec 28 17:57 ea-php72 -> /opt/cpanel/ea-php72/root/usr/bin/php -rwxr-xr-x 1 root root 28264 Aug 10 2017 lsphp -rwxr-xr-x 1 root root 28264 Aug 10 2017 php $ /usr/local/bin/php -v ea-php-cli Copyright 2017 cPanel, Inc. PHP 5.6.40 (cli) (built: Jan 24 2019 18:26:19) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies with the ionCube PHP Loader v4.7.5, Copyright (c) 2002-2014, by ionCube Ltd.
かといって/usr/local/bin/php
の実体を他のバージョンにすることもレンタルサーバーの都合上、無理ですよね。
色々と試行錯誤して諦めかけたその時に、神の啓示が...(笑)
(結局はものすごい簡単なことで対応することができました)
方法
まずは/usr/local/bin/
の中にあるPHPの一覧から自分の好きなバージョンを$HOME/bin/
にコピーします。(例ではPHP 7.1)
$ cp /usr/local/bin/ea-php71 $HOME/bin/php #もしくは$HOME/.local/bin/php
次に$HOME/.bash_profile
を編集します。
# .bash_profile # Get the aliases and functions if [ -f ~/.bashrc ]; then . ~/.bashrc fi # User specific environment and startup programs # PATH=$PATH:$HOME/.local/bin:$HOME/bin # 元々はこれ PATH=$HOME/.local/bin:$HOME/bin:$PATH # こちらに変更 export PATH
このように編集することで/usr/local/bin/php
よりも$HOME/bin/php
が先に探索されるようになるので、先ほどコピーしたPHPを利用することができるようになります。
$ which php ~/bin/php $ php -v PHP 7.1.26 (cli) (built: Jan 24 2019 17:47:13) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies with the ionCube PHP Loader (enabled) + Intrusion Protection from ioncube24.com (unconfigured) v10.2.4, Copyright (c) 2002-2018, by ionCube Ltd.
追記
書いてて思いついたんですけど、エイリアスとかでも対応できたかも?
$ alias php=/usr/local/bin/ea-php72 $ php -v PHP 7.2.14 (cli) (built: Jan 24 2019 17:28:26) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with the ionCube PHP Loader (enabled) + Intrusion Protection from ioncube24.com (unconfigured) v10.2.4, Copyright (c) 2002-2018, by ionCube Ltd. with Zend OPcache v7.2.14, Copyright (c) 1999-2018, by Zend Technologies
IQKeyboardManagerで親Viewが異なるUITextFieldを兄弟とみなす方法
課題
Qiitaで紹介されていて使ってみたIQKeyboardManager、すごい便利ですね。
IQKeyboardManagerは、デフォルトでは画像のように別の親Viewに属しているUITextFieldなどを兄弟と見なしてくれません。(つまりToolbarの↑↓でUITextFieldを移動できません。)
原因
IQKeyboardManagerのソースコードを見てみます。
UITextField兄弟間をToolbarの↑↓で移動するロジックは、兄弟をresponderViews()
という関数で探索していることが分かります。
@objc public var canGoNext: Bool { //Getting all responder view's. if let textFields = responderViews() { if let textFieldRetain = _textFieldView { //Getting index of current textField. if let index = textFields.index(of: textFieldRetain) { //If it is not first textField. then it's previous object canBecomeFirstResponder. if index < textFields.count-1 { return true } } } } return false }
responderViews()
はまず、全てのsuperViewからtoolbarPreviousNextAllowedClasses
の配列に含まれるクラスと一致するものを探索します。
もし一致するsuperViewが見つかった場合は、そのViewの全ての子Viewから兄弟を探索し、Viewの配列として返却します。
もし一致するsuperViewが1つも見つからなかった場合は、対象のUITextFieldと同じ階層にある兄弟だけを探索して返却します。
/** Get all UITextField/UITextView siblings of textFieldView. */ private func responderViews()-> [UIView]? { var superConsideredView : UIView? //If find any consider responderView in it's upper hierarchy then will get deepResponderView. for disabledClass in toolbarPreviousNextAllowedClasses { superConsideredView = _textFieldView?.superviewOfClassType(disabledClass) if superConsideredView != nil { break } } //If there is a superConsideredView in view's hierarchy, then fetching all it's subview that responds. No sorting for superConsideredView, it's by subView position. (Enhancement ID: #22) if let view = superConsideredView { return view.deepResponderViews() } else { //Otherwise fetching all the siblings //...
つまり、兄弟にしたいUITextFieldを全て、toolbarPreviousNextAllowedClasses
に含まれるクラスの子Viewにすれば、兄弟として判別してくれるというロジックのようです。
対策
まず、親View用のカスタムViewクラスを定義します。
今回はTextFieldsContainerView
という名前でクラスを定義しました。
import UIKit /// Class for letting IQKeyboardManager treat all text fields/views whose parent is this container view /// as a sibling so that it enables to handle all on its toolbar. /// We don't need this class all the time but useful when we have text fields/views that have different parent. /// See also: https://github.com/hackiftekhar/IQKeyboardManager/blob/master/IQKeyboardManagerSwift/IQKeyboardManager.swift#L1716-L1728 class TextFieldsContainerView: UIView { }
次に、IQKeyboardManagerのtoolbarPreviousNextAllowedClasses
に先ほど定義したTextFieldsContainerViewクラスを追加します。
IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses.append(TextFieldsContainerView.self)
最後に、TextFieldsContainerViewクラスをstoryboard/xibやコードで各UITextFieldの親Viewクラスになるように実装します。
BitbucketのコードをHerokuに自動デプロイする方法が超簡単だった
最近仕事が暇すぎて業務時間中に個人プロジェクトのコード書いてコミットしてたりしたんですが
プライベートレポジトリじゃないんでGithubのContribution activity(草の下の方とかに出るやつ)を同僚や上司に見られたらあまり良くないよなぁと思い
GithubのオープンレポジトリからBitbucketのプライベートレポジトリに移行しました。
元々はGithubのレポジトリとHerokuを連携させて自動デプロイの設定をしてたのですが
BitbucketとHerokuの自動デプロイ連携を今回やってみたので備忘録的なやつ。
(まさかこの直後にGithubがプライベートレポジトリを無料解放するとは、この時の僕は知る由もなかった...)
ではGithubのプライベートレポジトリが無料になったこの時代に、BitbucketとHerokuの自動デプロイ設定をしていきましょう。
ゴール
bitbucketにコミットすると自動的にpipelineがアプリケーションをビルドしてHerokuにデプロイする。
手順
- bitbucket-pipelines.yml の作成
- レポジトリに環境変数の設定
1. bitbucket-pipelines.yml の作成
参考までにこちらが私の bitbucket-pipelines.yml です。
image: php:7.1.3 pipelines: default: - step: name: Deploy to production deployment: production caches: - composer script: - apt-get update && apt-get install -y unzip git - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - composer install - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git HEAD
- bitbucketのデフォルトのPHPバージョンは7.1.1でしたが僕のプロジェクトにはPHP7.1.3が必要だったので7.1.3にしてます。
- それが原因か不明ですが、以下のようなエラーになるのでapt-getでgitもインストールしてます。
+ git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git HEAD bash: git: command not found
2. レポジトリの環境変数を設定
試してませんが、Repositoryの環境変数ではなくて、その下にあるDeploymentsの変数の方でも動作するかもしれません(し、そちらの方が適切かもしれません)
Deploymentsの変数の説明
Variables that can only be used in deployments to a specific environment.
HEROKU_API_KEY
の作成方法は公式ドキュメントを参照で。
iPhoneのWalletアプリにオリジナルのPassを登録する方法+α
iPhoneのWalletアプリ便利ですよね。僕も最近はコンビニでの支払いや自動販売機、電車の改札もWalletで済ませています。(個人的には使ってないですがポンタカードがWalletに登録できることを発見した時はテンション上がりました)
今回はそんなWalletアプリにオリジナルのPassを登録する方法を簡単にまとめてみました。 この記事では以下の内容について簡単に触れます。
- iPhoneのWalletアプリにオリジナルのPassを登録する方法
- PassがiBeaconに近づいたらロック画面に通知を表示する方法
- Pass(Wallet)にPush通知を送信する方法
オリジナルのPassを登録する
Passの作成方法
Passを作成するには最終的に.pkpass
という拡張子のファイルを生成することになります。
1. Pass Type IDの作成
2. Pass用のデータフォルダ作成
適用な場所に以下の構造のフォルダを作成します。名前はなんでも大丈夫です。
Sample.pass/
3. pass.jsonの作成
先ほど作成したフォルダにpass.json
というJSONファイルを作成します。Passの設定は全てこのpass.json
に記載されます。
{ ... "passTypeIdentifier" : "先ほど作成したPass Type ID", "teamIdentifier" : "DeveloperアカウントのTeam ID", ... }
Team IDはMember Centerで確認できます。
その他のキーの一覧
Top-Level Keysは必須のキー、Lower-Level Keysは任意のキーのようです。
4. .pkpassファイルの生成
.pkpassの生成にはsignpass
というコマンドラインツールが必要になります。まずはAppleのダウンロードリンクからsignpass
用のXcodeプロジェクトをダウンロードします。
ダウンロードしたらXcodeでそのプロジェクトを開きビルドを行うことでsignpass
コマンドラインツールがビルドされて利用できるようになります。
signpass
を作成することができたら以下のようにPass用のフォルダを指定してコマンドを実行します。Sample.pkpass
が生成されたら成功です。
$ ./signpass -p Sample.pass
あとは作成したSample.pkpass
をiPhoneにダウンロードするだけです。
ダウンロードの方法としては、EメールやSafariを使う方法や自分のiOSアプリを利用する方法などいくつかあります。
自分はアプリでやる方法しか試してないのですが、PassKitフレームワークを使って簡単に実装することができます。
その他の方法については以下のリンクをご参照ください。
Passのデザイン
Passには現在のところ以下の5つのタイプが存在しています。それぞれに可能なデザインが決まっているので、自分の作成するPassに合わせてデザインを決めます。例えば背景をかっこよくグラデーションにしたいと思ってもできるのはEvent ticketsだけ、というようなことがあったりするので注意です。
- Boarding passes
- Coupons
- Event tickets
- Store cards
- Generic
iBeaconに近づいたらロック画面に通知を表示する
Beaconと連携させることで例えば以下のような便利な機能を作ることができます。
- お店に近づいたらクーポンをロック画面に表示する
- 空港に到着したら航空券をロック画面に表示する
- ジムのドアの前でジムのカードキーをロック画面に表示する
ちなみにiBeaconではなくロケーションを利用することでも可能ですが、ロケーションの設定は1つのPassに最大10個までしか登録できません。
一方beaconの設定はというと、UUIDは同じく10個までしか登録できないものの、beacon端末のmajorとminorの値を変更することで同じUUIDのbeacon端末を作成することができるため、実質10個以上のbeaconを検知することができるようになっています。
Wallet Developer Guide: Pass Design and Creation
詳細なドキュメントは上記のRelevance Information Displays Passes on the Lock Screenの項に記載されています。
pass.jsonに以下の設定を追加します。
... "beacons": [ { "proximityUUID":"iBeacon端末の識別子", "relevantText":"ロック画面に通知する時に表示するテキスト" } ], ...
以上だけです。iBeacon端末を持っていない人はMacをiBeacon端末にして動作確認できます。
私は以下の記事で紹介されているGithubのプロジェクトをそのまま利用させていただきました。(感謝)
Pass(Wallet)にPush通知を送信する
Walletに登録済みのPassをPass提供者側から更新するためには、サーバからPush通知を送信する必要があります。
更新処理全体の流れは公式ドキュメントをご参照ください。
curlを例に送信方法は以下のようになります。
curl -v -d '{"aps":""}' -H "apns-topic:<Pass Type ID>" \ -H "apns-expiration: 1" -H "apns-priority: 10" \ --http2 --cert <path>/Certificates.pem:<password> \ https://api.push.apple.com/3/device/<device token>
注意点は、--cert
で指定する証明書はAPNsの証明書ではなくPass Type IDを作るときに生成した証明書を利用することです。
私はずっとAPNsの証明書を使って全然Push通知が届かず無駄な時間を費やしてしまいました。。
その他にもWalletのPassへのPush通知独自の注意点などが以下のstackoverflowによくまとめられているのでご参考までに。
KotlinConf 2018 - Shaping Your App's Architecture with Kotlin and Architecture Components
KotlinConf 2018のビデオでAndroidアプリの設計について面白いものがあったのでざっくりですがまとめてみました。
一昔前の設計のアプリをArchitecture ComponentsやCoroutinesを用いて再設計している実際の経験に基づいた話でとても参考になりました。
全体設計
UI, Domain, Dataのレイヤードアーキテクチャ。これは最近まあよく見るので特に目新しさはないですね。
- Data層:Repository, DataSource (Local/Remote)
- Domain層:UseCase
- UI層:ViewModel, Activity/View, XML
レイヤー間はCoroutinesでやり取りしてます。学習コストや利用の容易さからRxJavaではなくCoroutineを採択したようです。
Data層
Data層を構成するのは以下の3要素。
- Remote DataSource:(HTTP)リクエストを構築してサーバからデータを取得
- Local DataSource:ディスクにデータを保存
- Repository:上記2種類のDataSourceを利用してデータの取得と保存; 任意でインメモリにデータをキャッシュ
インメモリキャッシュは専用のシングルトンクラスとか作ったりすることが多かったんですが、Repositoryでインメモリキャッシュを持つという設計は目から鱗でした。こうすることでレイヤードアーキテクチャの他の層からインメモリキャッシュを参照するようなコードも防止できるし理にかなってる気がします。ただRepositoryのプロパティに単純に持つとしたらRepositoryのライフサイクルがどうなってるのか気になります。
また設計にはあまり関係ないですが、生成されるオブジェクトの数を減らす(?)テクニックとしてinline classが紹介されていました。 下の写真の例では、1つのRepositoryに第2引数だけが異なるpostCommentという同名メソッドを定義する方法として、第2引数のLong型の変数をそれぞれ別のinline classにしています。 つまりプリミティブ型の変数に名前をつけて区別するようなものでしょうか(知らんけど)。
Domain層
UseCaseの責務は
- ビジネスロジックに基づいてデータを処理する
- たった1つのタスクを持つ
- チームのルールとして1つのUseCaseには1つだけのpublicメソッド、他は全てprivateメソッド
- その1つのpublicメソッドはinvokeメソッドにして関数オブジェクトとして扱う
- ユーザのログイン状況の確認などはUseCaseの責務(UseCaseがLoginRepositoryを扱う)
UI層
ViewModelの責務は
- UIで表示されるデータを公開(LiveData)
- ユーザアクションに基づいてUseCaseのアクションを実行
- Coroutinesの開始とキャンセル
ViewModelはActivityとXMLの2箇所から参照される。 ViewModelとXMLの間のデータのやり取りはデータバインディングを用いる。
- ViewModelは関連するいくつかのUseCaseや他の引数をコンストラクタで受け取ってimmutablityを保つ
- ViewModelProvidersで生成するViewModelはデフォルトでは引数を受け取れないため、Factoryを拡張する
- 全てのViewModelには対応するFactoryを作成する
動画では、ViewModel自身とstoryIDのimmutabilityを保つために同じActivityクラスが複数インスタンス存在するとしても、それぞれ別インスタンスのViewModelを持つ、というニュアンスのことを言ってる?ような気がするんだけど、これはViewModelのそもそもの目的からちょっと外れる気がするのでどういうことかよく理解できない。分かる人教えて欲しい。
- Activityからの編集を防止するためにViewModelではMutableLiveDataをprivateにしてImutableなLiveDataを宣言して公開する
- ImutableなLiveDataのgetterはMutableLiveDataをそのまま返却する
- URLを開いたりトーストの表示などは、独自のEvent型のLiveDataで扱う
- Event型を利用することで、再度observeされた時に重複してイベントが発火しないようにする
- Event型の詳細については以下の別ブログ参照
まとめ
- よく見るレイヤードアーキテクチャ+Coroutinesを採択
- インメモリキャッシュはRepositoryの責務
- UseCaseは最小単位で実装して関数オブジェクトとして扱うといい感じ
- ViewModelは独自のFactoryクラスを実装してUseCaseやその他データをコンストラクタで受け取ってimmutableに保つ
- 一度きりのUIイベント等をViewModelで管理する手段として独自のEvent型のLiveDataを利用する
こんなところでしょうか。 動画の中では他にも、Data層のRepositoryがKotlinで書かれたDomain層からだけじゃなく古いJavaコードやUI層のコードからも参照される状況の対応策や、複雑なデータ構造のデータを生成するための拡張実装についてなど、有益そうな情報がたくさんありました。
XCTestでFirebase Realtime DatabaseのAPIをモックしてレスポンスを偽造する
仕事でFirebase Realtime Databaseを利用していてユニットテストを書くためにモックについて調べてみるとこちらの記事に出会いました。
モック以外の内容も書かれており素晴らしい内容ですね。とても参考になります。
今回はもう少しお手軽にFirebase Realtime Databaseのレスポンスをモックする方法を考えてみました。
複雑なことせずになんとかならんのか、という方のお役に立てれば何よりです。
テスト対象コード(サンプル)
class FirebaseDatabaseClient { private let databaseReference: DatabaseReference init(with databaseReference: DatabaseReference) { self.databaseReference = databaseReference } func readSample(completion: @escaping ([String]) -> Void) { self.databaseReference .child("sample") .observeSingleEvent(of: .value) { snapshot in var sampleList: [String] = [] // 省略... completion(sampleList) } } }
- DatabaseReferenceのインスタンスをコンストラクタで受け取る
- "sample"というパスのデータを
observeSingleEvent
で1度だけ取得 - レスポンスのsnapshotをごにょごにょしてcompletionハンドラに引き渡し実行
といったような実装になっています。
テストコード
import XCTest import FirebaseDatabase @testable import Sample_App class FirebaseDatabaseClientTests: XCTestCase { func testReadSample() { let client = FirebaseDatabaseClient(with: MockDatabaseReference()) let expectaion = expectation(description: #function) client.readSample { sampleList in XCTAssertTrue(sampleList.contains("something")) expectaion.fulfill() } wait(for: [expectaion], timeout: 0.5) } } // MARK: - Override Firebae Realtime Database private class MockDatabaseReference: DatabaseReference { override func child(_ pathString: String) -> DatabaseReference { return self } override func observeSingleEvent(of eventType: DataEventType, with block: @escaping (DataSnapshot) -> Void) { let snapshot = MockSnapshot() DispatchQueue.global().async { block(snapshot) } } } private class MockSnapshot: DataSnapshot { override var value: Any? { return ["key_1": "value_1", "key_2": "value_2"] } }
1つ1つ軽く解説します。
テストケース
func testReadSample() { let client = FirebaseDatabaseClient(with: MockDatabaseReference()) let expectaion = expectation(description: #function) client.readSample { sampleList in XCTAssertTrue(sampleList.contains("something")) expectaion.fulfill() } wait(for: [expectaion], timeout: 0.5) }
まずはテストケースから。1行目でテスト対象のクラスを作成しますが、その際に引数としてFirebaseのDatabaseReferenceクラスのモックオブジェクトを渡します。モックオブジェクト自体の解説は後述します。
またデータベースからのデータ取得は非同期であるはずなのでXCTestExpectation
を用います。
DatabaseReferenceのモック
private class MockDatabaseReference: DatabaseReference { override func child(_ pathString: String) -> DatabaseReference { return self } override func observeSingleEvent(of eventType: DataEventType, with block: @escaping (DataSnapshot) -> Void) { let snapshot = MockSnapshot() DispatchQueue.global().async { block(snapshot) } } }
こちらが先ほども出てきたFirebaseのRealtimeDatabaseのモックオブジェクトの定義です。今回はテスト対象のクラスでobserveSingleEvent
を利用しているのでその関数と、child
関数もオーバーライドしています。
child
関数をオーバーライドする理由としては、アプリコードの方でデータベースの特定のパスのデータを取得する場合にchild("path")
というAPIが実行されると、テスト対象クラスのコンストラクタに引き渡したDatabaseReferenceオブジェクトとは別のインスタンスが使われてしまってモックすることができません。
なので今回はchild
が何度実行されようともモックオブジェクトを返すようにして、どんなpathのデータを取得しようともモックのDatabaseReferenceクラスが利用されるようにしています。もしpathによって返却するデータや処理を変えたい場合は、以下のようにオーバライドメソッドの中でpathString
を見て別のオブジェクトを返却したりとかでできるかもしれません。
override func child(_ pathString: String) -> DatabaseReference { switch (pathString) { case "hoge": return HogeDatabaseReference() case "fuga": return FugaDatabaseReference() } }
DataSnapshotのモック
private class MockSnapshot: DataSnapshot { override var value: Any? { return ["key_1": "value_1", "key_2": "value_2"] } }
最後に実際のレスポンスのクラスであるDataSnapshotのモックオブジェクトの定義です。DataSnapshotのvalue
プロパティは本来readonlyなのでそのままではvalue
を自分好みにすることができません。そこでvalue
のプロパティをオーバーライドして、テストで利用したいデータを返却します。
このモックオブジェクトを先ほどのDatabaseReferenceクラスのモックオブジェクトのobserveSingleEvent
で利用することで、Realtime Databaseから指定したデータが返却されているように偽造することできるようになります。
MapKitの地図をGoogleMap風にカスタマイズする
簡単に現在地の周辺や特定の場所を表示したりするだけならGoogle Map SDKを使えば良さそうなんですが、
アノテーションを充実させたり、検索機能が必要だったりするとMapKitを使う方がベターだったりします。
(知ってる限りではGoogle Mapでの検索はPlace APIで課金が発生する)
とはいえUberみたいなイケてる地図を表示したいという要求がデザイナーから来ることも無きにしも非ず。
そういう場合は、後述する方法でMapKitのメリットを享受しながら見た目だけGoogle Map風に変更する、のが一つの手かと思います。
方法
MapKitの地図はオーバーレイの画像をMKTileOverlay
で指定することができるようになっているのでそれを加工します。
MKTileOverlay - MapKit | Apple Developer Documentation
私は今回調べるまで聞いたこともなかったんですが、タイルオーバーレイというのは地図のタイル(小さい単位の区域)を表示する際にタイルのデータを(サーバから)取得して地図の上に表示する仕組みのようです。
タイルのデータの取得先は自分でホストしたり以下のようなプロバイダーからAPIで取得したり、といった感じ。
Tile servers - OpenStreetMap Wiki
Google Maps APIs Styling Wizard
このタイルオーバーレイの仕組みを利用してGoogle Map風のタイルを取得するSDKがこちら👏
Googleが提供しているGoogle Maps APIs Styling Wizardを利用してタイルデザインのJSONファイルをまずは自分で作成しSDKに渡します。
SDKはそのJSONファイルのパースして、Googleのタイルサーバへとリクエスト、取得したタイルの表示までしてくれるようです。
Snazzy Maps
Styling Wizardを利用して自分好みのタイルデザインを作ることも可能ですが、
クールでセクシーなデザインがまとまっているサイトがあるので、そちらから気に入ったものを選ぶこともできます🤩
参考
色々と調べる中で以下のブログを見つけ、まさにこれだ!となりました。感謝。