- 投稿日:2019-04-11T22:12:35+09:00
FirebaseとiAd.frameworkの連携についてわかったことメモ
やりたいこと
BigQuery内で
Search Adsのコンバージョンを集計させたいFirebaseのApple Search Adsに関するドキュメント
Firebaseの集計データはBigQueryのデータをいい感じに集計して表示してくれている。Google広告と連携させていれば、どの広告をタップしてインストールしてくれたのかということがわかる。しかしAppleの
Search Adsに関しては、iOSアプリ側から何かしら入れてあげないと集計できていないことがわかった。具体的に送られるイベントや、何が送られるのかはこちらを参照する
https://support.google.com/firebase/answer/6317518?hl=jaApple Search Adsに関して引用すると
Apple Search Ads Apple Search Ads のクリックによってアプリがインストールされた場合、firebase_campaign イベントが上記のパラメータとともに記録され、キャンペーン関連のイベントについては次のデータが表示されます。 参照元 = Apple メディア = search キャンペーン = <iad キャンペーン名> Apple Search Ads をトラッキングするには、アプリ用の Xcodeプロジェクトファイルに iAd フレーム ワークを追加する必要があります。このようにAppleに関しても集計できそうということがわかる
iAd.frameworkを入れればFirebaseがいい感じに集計してくれそうということがわかるBigQueryの調べ方
具体的にBigQueryに何が送られてくるのかは、こちらを参照する
https://support.google.com/firebase/answer/7029846?hl=ja&ref_topic=7029512traffic_sourceを調べる具体例
traffic_sourceとは ユーザーを最初に獲得したトラフィック ソースの名前。このフィールドは当日テーブルでは使用されません。叩くクエリ例
SELECT traffic_source.source, COUNT(*) FROM `{テーブル名}` GROUP BY traffic_source.source LIMIT 100下記のような感じで、データの結果が帰ってくる。この例では、テーブルを検索し、どの流入元が一番多いのか調べるために、数を数えた
おそらくSearch Adsで広告を打った場合、traffic_source.sourceにAppleと入ってきてくれるはずiAd.framework 導入について
iAd.frameworkのドキュメントには下記のようにフレームワークを入れると書いてある
iAd framework The iAd framework is bundled with Xcode. Follow these steps to add the iAd framework to the Xcode project file for your app: 1. Go to your target view and select General. 2. Scroll down to a section called Linked Frameworks and Libraries and click the plus (+) icon. 3. In the dropdown menu, search for iAd. 4. Select iAd.framework and click the Add button.ドキュメントに従いtargetの Generalを選択し、Linked Frameworks and Librariesの項目の下の方のプラスボタンを押して,
iAd.frameworkを導入する問題点
iAd.frameworkを導入するだけで、本当にFirebaseが集計してくれるのか不安、Search Adsに課金しない限り調べる方法はないのか探していた改善策
Xcode デバックコンソールでログを見る
ドキュメントリンク
https://firebase.google.com/docs/analytics/ios/events?hl=ja
Firebase Debug Viewで送信されている イベントを確認する
気になるログを発見した
iAd.frameworkを導入するとログが変わっていることがわかった導入前
xxxxApp[00000:000000] Ad framework is not linked. Search Ad Attribution Reporter is disabled. xxxxApp[00000:000000] No data to upload. Upload task will not be scheduled xxxxApp[00000:000000] Analytics enabled導入後
xxxxApp[00000:000000] Scheduling Search Ad Report timer xxxxApp[00000:000000] Search Ad campaign report alarm scheduled to fire in approx. (s):結果
無事にFirebaseで送られてBigQuery内で確認することができた
(おまけ)iAd.framework 導入によってアプリ内に、端末がどのキャンペーン経由でインストールされたのか取得する
Firebaseの連携とは全く関係ないのだが一応、必要な人もいるかも知れないので共有
iAd.frameworkを導入すると利用できるようになる関数ADClient.shared().requestAttributionDetailsを用いて,
辞書attributionDetailsの中に広告の情報が入っている。ここでとりだして、各自データを集計しているサーバーにおくればいいのではないかと思う詳細に関してはAppleのiAd.frameworkのドキュメントに書いてある
import iAd import UIKit class TestViewController: UIViewController{ override func viewDidLoad() { super.viewDidLoad() ADClient.shared().requestAttributionDetails({ (attributionDetails, error) in if error == nil { for (type, adDictionary) in attributionDetails! { var attribution = adDictionary as? Dictionary<AnyHashable, Any>; let params = [ "appID": "self.appData.appID", "iadAdgroupId": attribution?["iad-adgroup-id"] as? String as Any, "iadAdgroupName": attribution?["iad-adgroup-name"] as? String as Any, "iadAttribution": attribution?["iad-attribution"] as? String as Any, "iadCampaignId": attribution?["iad-campaign-id"] as? String as Any, "iadCampaignName": attribution?["iad-campaign-name"] as? String as Any, "iadClickDate": attribution?["iad-click-date"] as? String as Any, "iadConversionDate": attribution?["iad-conversion-date"] as? String as Any, "iadCreativeId": attribution?["iad-creative-id"] as? String as Any, "iadCreativeName": attribution?["iad-creative-name"] as? String as Any, "iadKeyword": attribution?["iad-keyword"] as? String as Any, "iadLineitemId": attribution?["iad-lineitem-id"] as? String as Any, "iadLineitemName": attribution?["iad-lineitem-name"] as? String as Any, "iadOrgName": attribution?["iad-org-name"] as? String as Any ] print("////////////////////") print(params) } } }) } }参考文献
iAd frameworkをいれるだけでいいということに気がついた記事
https://groups.google.com/forum/#!topic/firebase-talk/dUxH5VfHG2k
- 投稿日:2019-04-11T20:12:42+09:00
iOSアプリケーションにおける、Launch Screenの役割
iOS向けアプリケーションにおける、Launch Screenの役割
iOS向けアプリケーションを開発していると、Launch Screenを作る機会があるはずです。
僕もいくつか作ってきました。
Launch ScreenについてGoogle検索をしていると、Appleが意図した使い方ではない行為を推奨しようとしている記事をいくつか見つけてしまったので、Apple Human Interface Guidelinesにおける記述を合わせて、なるべくAppleの意図に沿う形で、Launch Screenの役割について説明していこうと思います。
1. Launch Screenとは
iOS向けアプリケーションという文脈で「Launch Screen」とは、旧来型の呼び名で言う、「スプラッシュスクリーンのようなもの」という説明で大筋を説明することができます。
スプラッシュスクリーンをご存じない方のために、説明させていただきますと、スプラッシュスクリーンとはアプリケーションが起動中の間の画面表示全般を指します。
起動中に表示される画面全般ですね。
2. Appleの定義するLaunch Screenとは
AppleはiOS向けアプリケーションに一貫したデザイン性・ユーザー体験を持たせるために、 Apple Human Interface Guidelinesというドキュメントを整備しています。
その中に、Launch Screenについての定義がありましたので、ここに転記します。
(https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/)(原文版)
1. It’s solely intended to enhance the perception of your app as quick to launch and immediately ready for use.
2. The launch screen isn’t an opportunity for artistic expression.
3. Design a launch screen that’s nearly identical to the first screen of your app.
4. Don’t advertise. The launch screen isn’t a branding opportunity.(邦訳版)
1. アプリの起動をすばやく開始し、すぐに使用できるようにすることを目的としています。
2. ローンチスクリーンは芸術的表現の機会ではありません。
3. アプリの最初の画面とほぼ同じ起動画面を設計します。
4. 宣伝しないでください。起動画面はブランディングの機会ではありません。細かい説明
1つ1つ、細かく説明させていただきたいと思います。
a. すぐに使用できるようにデザインしなければならない
Launch Screenを安直に「スプラッシュスクリーン」と捉えてしまうと、よく陥りがちなのが
「とりあえずアプリのロゴをデザインする」
という解決策だと思います。
実際このパターンでやっているサービスは結構あります。
が、この行為はApple的には推奨ではありません。
iOS端末をお使いの方ならわかると思いますが、iOS端末の標準アプリ(App Store、iTunes Store、設定、メッセージ、メール、Map・・・etc)を開くたびにAppleのロゴが表示されることはありません。
b. 芸術的表現のためのものではない
これは、
a. すぐに使用できるようにデザインしなければならないの言い換えと捉えることもできるのですが、そもそもAppleはLaunch Screenの目的を「アプリの起動をすばやく開始し、すぐに使用できるようにすること」
としているため、Launch Screenでユーザーが待つ必要のある芸術性のある表現を行うことを してはならないと明言しています 。(禁止しています)
c. アプリの最初の画面とほぼ同じ画面にしよう
Launch Screenをアプリの最初の画面とほぼ同じ画面にすることにより、アプリが高速に動作しているようにユーザーに思わせることができます。
公式以外のアプリだと、Facebookや、Google Map、Slack、Whats Appなんかがとてもうまくできてますね。
d. ブランディングのために使ってはならない
無計画にロゴを出したりサウンドを出したりしてはいけません。Appleが明確に禁止しています。
これも
a. すぐに使用できるようにデザインしなければならないの言い換えです。AppleはHuman Interface Guidelinesで下記のように述べています
Don’t include logos or other branding elements unless they’re a static part of your app’s first screen.
邦訳 : ロゴや他のブランド要素がアプリの最初の画面の静的な部分でない限り、それらを含めないでください。
Launch Screenで安易にロゴを出すことによってブランディングを行うことは避けるべきです。
こぼれ話: そもそもなんでスプラッシュスクリーンは嫌われたのか
ユーザーインターフェース、ユーザーエクスペリエンスの権威とも言える、ヤコブ・ニールセンという方が、この問に対して一定の答えを提供しています。
https://u-site.jp/alertbox/interaction-cost-definition
彼は、ユーザビリティの究極の目標を下記のように述べています。
理想的には、ユーザーがサイトに行ったら、探している答えがすぐ目の前にあるといい。それはつまりインタラクションコストがゼロになるということで、ユーザビリティという領域の究極の目標はそこにあるからである。
インタラクションコストを彼は下記のように定義しています。
インタラクションコストとは、ユーザーが目標を達成するため、サイトとインタラクトするのに必要な、精神的・肉体的な努力の総計である。
上記の前提の元、スプラッシュスクリーン説明したものがこちらです。
アプリ起動後、最初に現れるのはスプラッシュ画面である:
この時点でインタラクションコストに含まれるのは、フラッシュ画面が消え、このアプリの最初の操作可能な画面のためのスペースができるのを数秒待つことである
つまり、スプラッシュスクリーンはうまくデザインしないと
「存在するだけでインタラクションコストを発生させる」
ものだと彼は説明しています。
この考え方に納得できる人は多いのではないでしょうか。
参考
https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/launch-screen/
- 投稿日:2019-04-11T19:44:48+09:00
UIColorで使っているRGBの値を取得する
概要
UIColor(例えば、UIColor.red)で使われているRGB、alphaの値を取得したい
前提
- Xcode 10.1
- Swift 4.2
tl;dr
CGColorから取ってくる。(lldb) po UIColor.yellow.cgColor.alpha 1.0(lldb) po UIColor.yellow.cgColor.components ▿ Optional<Array<CGFloat>> ▿ some : 4 elements - 0 : 1.0 - 1 : 1.0 - 2 : 0.0 - 3 : 1.0print(UIColor.red.cgColor.components![0]) // Red 1.0extend!
- UIColorのextensionとして定義して便利に使う
import UIKit extension UIColor { var redColor: CGFloat? { return self.cgColor.components?[0] } var greenColor: CGFloat? { return self.cgColor.components?[1] } var blueColor: CGFloat? { return self.cgColor.components?[2] } var alpha: CGFloat { return self.cgColor.alpha } }print(UIColor.red.redColor) // Optional(1.0) print(UIColor.red.blueColor) // Optional(0.0) print(UIColor.red.greenColor) // Optional(0.0) print(UIColor.red.alpha) // 1.0
- 投稿日:2019-04-11T19:31:03+09:00
Swiftで特定の複数の型のみを許容するArray
1つの型を許す配列はもちろん初心者でもすぐ出来ると思います。
では例えば、IntとDoubleのみを許す数値の配列を用意したい場合、どうすればいいでしょう。
手段と長短をまとめました。手段
Enum を利用
enum EnumNumber { case int(Int) case double(Double) } var enumNumbers: [EnumNumber] = [.int(10), .double(2.0)]Protocol を利用
protocol ProtocolNumber {} extension Int: ProtocolNumber {} extension Double: ProtocolNumber {} var protocolNumbers: [ProtocolNumber] = [Int(10), Double(2.0)]長所と短所
switch で値を取り出す
Protocolを使用した場合だと、全パターンを書いたとしてもdefault行を書かなくてはならないので、少し安全ではないコードになってしまうかも。// Enum switch enumNumbers.first! { case .int(let value): print(value) case .double(let value): print(value) } // Protocol switch protocolNumbers.first! { case let value as Int: print(value) case let value as Double: print(value) default: print("exception") }型が分かっている物の値を取得
Protocolだとキャストするだけで値が取れる( optional もしくは forced unwrap ですが )のに対し、
Enumだとおそらくif case文を書かないといけないのではと思います。となるとインデントが深くなってしまいます。
インデントが深くなるのを回避するために パターン2 のように computed property を生やす手がありますが、もしこうするならば型の数だけ用意しなければなりません。// Enum パターン1 if case .int(let value) = enumNumbers[0] { let enumInt = value } // Enum パターン2 extension EnumNumber { var integerValue: Int? { if case .int(let value) = enumNumbers[0] { return value } return nil } } let enumInt = enumNumbers[0].integerValue // Protocol let protocolInt = enumNumbers[0] as? Int生成する時
大きな違いは、
Protocolの場合は Int か Double か分かる必要が無い という点です。
Enumの場合は Int か Double か把握した上で Enum を生成 する必要があります。
明らかにEnumのほうが面倒くさくなると思います。let unknownValue: Any = Int(123) // Enum パターン1 // このパターンだと、unknownValue が Double の場合、追加されない if let intValue = unknownValue as? Int { enumNumbers.append(.int(intValue)) } // Enum パターン2 extension EnumNumber { init?(_ value: Any) { switch value { case let value as Int: self = .int(value) case let value as Double: self = .double(value) default: return nil } } } if let value = EnumNumber(unknownValue) { enumNumbers.append(value) } // Protocol if let value = unknownValue as? ProtocolNumber { protocolNumbers.append(value) }ネストしたい
Swift の設計上
Protocolはネストできません。class Sample { // OK enum EnumNumber {} // error: protocol 'ProtocolNumber' cannot be nested inside another declaration protocol ProtocolNumber {} }まとめ
主観で ○ × つけました。
Enum Protocol switch で値を取り出す ○ × 型が分かっている物の値を取得 × ○ 生成する時 × ○ ネストしたい ○ × まあこんな色々書きましたが、基本的には
Protocolを使うべきですね笑参考
環境
- Swift 5.0
他にもなにかありましたら教えてください!
- 投稿日:2019-04-11T18:53:29+09:00
AutoLayout+StackViewで自動可変行テーブルビューを実装する
従来、UITableViewCellの内容に応じて行の高さを算出して可変にする実装はかなりめんどくさかった。
AutoLayoutとStackViewで自動的に可変になるように実装してみた。
AutoLayoutはSnapKitを使った。
- Xcode 10.1
- Swift 4.2
サンプルコードは以下に配置
https://github.com/yumemi-ajike/AutoFlexibleRowHeightUITableViewCellの内部レイアウト
パターン
UITableViewCellのサブクラスとしてTableViewCellを追加し、
イメージが縦に配置されるVerticalTableViewCellと左横に配置されるHorizontalTableViewCellをそれぞれTableViewCellを共通の親としてサブクラス化した。
イメージの縦配置 イメージの横配置 テキストのみ1行 テキストのみ複数行 テキスト1つのみ 組み合わせはもっとたくさんあるけどサンプルコードをビルドしたら確認可能なので割愛する。
イメージの縦配置
VerticalTableViewCellクラスで実装している。ビュー構造
UITableViewCell.contentViewに以下のビュー構造で追加していて、
それぞれのUIImageViewやUILabelのisHiddenを変更することでレイアウト内容を変えている。
- UIStackView
- UIImageView
- UILabel
- UILabel
- UILabel
- UIView(下部separator)
表示順
addArrangedSubviewした順番に表示される。TableViewCell.swiftfinal class VerticalTableViewCell: TableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(stackView) stackView.addArrangedSubview(thumbnailImageView) stackView.addArrangedSubview(titleLabel) stackView.addArrangedSubview(dateLabel) stackView.addArrangedSubview(descriptionLabel) stackView.snp.makeConstraints { (make) in make.edges.equalToSuperview().inset(insets) } thumbnailImageView.snp.makeConstraints { (make) in make.width.equalTo(stackView.snp.width) // 16 : 9 and hidden priority make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue) } } ... }
stackView.axis = .verticalで縦方向に整列するようにし、stackView.distribution = .equalSpacingで項目間のマージンを均一にしている。
セル内側のマージンは以下で定義してstackViewのedgesにinsetする形でセットしている。TableViewCell.swiftlet insets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16)イメージサイズ
ImageViewの表示サイズの制約は16:9で表示するようにし、
非表示時の対応としてpriorityを.defaultLowにセットしている。
これがないとAutoLayoutの警告が出るので注意。TableViewCell.swiftthumbnailImageView.snp.makeConstraints { (make) in make.width.equalTo(stackView.snp.width) // 16 : 9 and hidden priority make.height.equalTo(stackView.snp.width).multipliedBy(0.5625).priority(UILayoutPriority.defaultLow.rawValue) }イメージの横配置
HorizontalTableViewCellクラスで実装している。ビュー構造
UITableViewCell.contentViewに以下のビュー構造で追加。
- UIStackView
- UIView
- UIImageView
- UIStackView
- UILabel
- UILabel
- UILabel
- UIView(下部separator)
イメージの右横にテキスト行を配置する必要があるため、
UIStackViewの入れ子構造とすることで実現している。
また UIImageView がひしゃげてしまったり伸びてしまったりすることを避けるため、imageBaseViewを追加することで回避している。TableViewCell.swiftfinal class HorizontalTableViewCell: TableViewCell { private let imageBaseView = UIView() ... override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { let horizontalStackView = UIStackView() super.init(style: style, reuseIdentifier: reuseIdentifier) horizontalStackView.axis = .horizontal horizontalStackView.alignment = .top horizontalStackView.distribution = .fill horizontalStackView.spacing = 8 contentView.addSubview(horizontalStackView) stackView.addArrangedSubview(titleLabel) stackView.addArrangedSubview(descriptionLabel) stackView.addArrangedSubview(dateLabel) horizontalStackView.addArrangedSubview(imageBaseView) imageBaseView.addSubview(thumbnailImageView) horizontalStackView.addArrangedSubview(stackView) horizontalStackView.snp.makeConstraints { (make) in make.edges.equalToSuperview().inset(insets) } ... } }親のStackViewは
horizontalStackView.axis = .horizontalで横方向に整列するようにし、
horizontalStackView.alignment = .topで上揃えになるようにしている。イメージサイズ
ImageViewの表示サイズは固定で64 x 64になるように制約を設けている。
TableViewCell.swiftimageBaseView.snp.makeConstraints { (make) in make.width.equalTo(64) make.height.greaterThanOrEqualTo(imageBaseView.snp.width).priority(UILayoutPriority.defaultLow.rawValue) } thumbnailImageView.snp.makeConstraints { (make) in make.size.equalTo(64) make.center.equalToSuperview() }文字や画像のセット
各ビューに文字や画像などの情報を入れ、表示されないビューの
isHiddenを変更して表示を切り替えている。TableViewCell.swiftfunc configure(with item: Item) { thumbnailImageView.kf.setImage(with: item.imageUrl) isThumbnailHidden = item.imageUrl == nil titleLabel.text = item.title titleLabel.isHidden = item.title == nil dateLabel.text = item.date dateLabel.isHidden = item.date == nil descriptionLabel.text = item.description descriptionLabel.isHidden = item.description == nil }動作確認
TableViewController.swiftでTableViewCell.Itemの配列を作ってパターンを網羅するようにしている。TableViewController.swiftprivate let items = [TableViewCell.Item(title: "Short title", date: nil, description: nil, imageUrl: nil), TableViewCell.Item(title: "Short title", date: "April 7th, 2019", description: nil, imageUrl: nil), ... 省略 ... TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.", date: "April 7th, 2019", description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.", imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image")), TableViewCell.Item(title: "Long title: A view that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.", date: "April 7th, 2019", description: "The appearance of labels is configurable, and they can display attributed strings, allowing you to customize the appearance of substrings within a label. You can add labels to your interface programmatically or by using Interface Builder.", imageUrl: URL(string: "https://via.placeholder.com/320x180.png?text=Sample+Image"))]感想
かなりシンプルになったし、コード量が激減するので嬉しい!
行高計算のめんどくささからも解放されて喜ばしいけど、AutoLayoutの制約エラーを解決していくのがメイン作業になってこれはこれで慣れが必要。。。
UIStackViewを入れ子構造にしたときにiOS10以前で発生する制約の不整合とかもあるのでその辺の解決にちょっと時間かかった。
- 投稿日:2019-04-11T16:38:19+09:00
Adobe XDでiOSアプリのスプラッシュ画像を一括書き出しする
Adobe XDとは
Adobe XDはAdobeからリリースされているプロトタイピングツールです。
Adobeアカウントを作成(無料)すれば誰でも使うことができます。(無料!!!)
登録はこちらから。以前はSkecthを使用してワイヤフレームの作成などをしていましたが、買い切り型からサブスクリプション型に移行されてしまい、出費も気になっていたところ、試しに使ってみたらすごく使いやすかったので、こっちに乗り換えました。
デザイナーでなくても直感的にワイヤフレームを作成できるので、エンジニアにもおすすめです。
自分は職種上プロジェクトマネジャーになりますが、お客さんの要望からXDでワイヤフレームを作成し、要件に落とし込んでからデザイナーへXDのファイルをそのまま共有しているので、要件定義からデザイン作成の工程がスムーズに出来ます。実装指示書や設計書への落とし込みも楽チンです。今回やること
XDはプロトタイピングツールとして非常に優秀ですが、ちょっとした画像の作成なども簡単に出来てしまうので、今回は用意するのが以外に面倒なアプリのスプラッシュ画像(起動画像)の作成をXDでやってみたいと思います。
最近ではStory boardでスプラッシュ画像を設定するプロジェクトもありますが、まだImages.xcassetsでデバイス毎に画像を設定しているプロジェクトもあるかと思いますので、今回は後者向けの内容の記事になります。
必要なスプラッシュ画像
デバイス サイズ 倍率 iPhone Xs Max 1242 x 2688px 3x iPhone Xr 828 x 1792px 2x iPhone X/iPhone Xs 1125 x 2436px 3x iPhone 6/7/8 Plus 1242 x 2208px 3x iPhone 6/7/8 750 x 1334px 2x iPhone 5/SE 640 x 1136px 2x iPhone 4/4S 640 x 960px 2x プロジェクトの作成
まずはXDをインストールして、XDアプリを起動し、新規プロジェクトを作成します。
適当に上メニューのiPhone X/XSを選択します。
新規プロジェクトが作成され、iPhone X/XSと書かれたアートボードが表示されたと思います。
スプラッシュを作成
作成されたアートボードに適当にオブジェクトを配置して画面を作成していきます。(適当に配置しました)
デバイスサイズ毎にアートボードを作成
iPhone X/XSのスプラッシュ画像が出来たので、他のディスプレイサイズのアートボードも作成して、スプラッシュ画像を作成します。
残念ながら、iPhone 4/4S用のスクリーンはXDには用意されていないので、適用なアートボードを追加して、サイズを変更します。
アートボードの名前はダブルクリックで変更可能です。
追加したアートボードにも同様にオブジェクトを配置していきます。
コピペなどでもいけますが、シンボル化などを行うとより効率的に配置することが出来ます。(今回は説明を割愛します。)
オブジェクトのサイズなどはデバイス毎に見やすいように調整してください。
画像を書き出す
スプラッシュの作成が終わったら、画像を書き出します。
メニューからファイル > 書き出し > すべてのアートボードを選択
保存先は適当に選択してください。
書き出し先にiOSを選択し、デザイン倍率は1xを選択してください。
設定が終わったらすべてのアードボードを書き出しを選択してください。
指定したフォルダにすべてのアートボードの1x~3xサイズの画像が書き出されました。
あとはXcodeに設定するなりなんなりすればOKです!おわりに
本来のXDの使い方とは異なりますが、こんなことも簡単に出来てしまうくらいXDは使いやすいです。
こういう形で管理できれば新しいデバイスが登場した際や、デザインを変更したいときに書き出す手間などが少なくできるのでいいのではないかなと思いました。XDはプロトタイプの作成にとても優れているので、興味を持った方はぜひ使ってみていただければと思います。
職場でパワーポイントなどで一所懸命ワイヤーを作ってる人を見かけたらXDを勧めていますw参考資料
- 投稿日:2019-04-11T13:34:14+09:00
[Swift4] バックグラウンドでオーディオ再生する
iOSアプリでオーディオ再生を実装する際に、アプリがバックグラウンドになっても再生し続けるようにする設定手順です。
必要な設定は2つです。
- Capabilities設定で、Background Modes - Audio をONにする。
- AVAudioSession のカテゴリをPlaybackに設定する。
Capabilities設定からBackground Modesを設定する
プロジェクトファイルを選択肢、「TARGETS」からアプリ名をクリックし、「Capabilities」を選択します。
この中の、 「Background Modes」をON に変更します。
さらに、「Modes」の中から、「Audio. AirPlay. and Picture in Picture」のチェックボックスをONにします。以下の画面は、これらの設定をした後の画面です。
AVAudioSession のカテゴリをPlaybackに設定する
続いて、AVAudioSessionの設定を行います。
AVAudioSessionは、アプリでオーディオをどのように扱うかを宣言するためのオブジェクトです。
例えば、他のアプリで音声再生をしているときの挙動や、バックグラウンドでの音声再生の挙動などを扱うことができます。
AVAudioSession - Apple Developer DocumentAppDelegateにて、AVAudioSessionのCategory設定を変更してやります。
AppDelegate.swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. /// AVAudioSessionCategory設定 let session = AVAudioSession.sharedInstance() do { // CategoryをPlaybackにする try session.setCategory(.playback, mode: .default) } catch { // 予期しない場合 fatalError("Category設定失敗") } // session有効化 do { try session.setActive(true) } catch { // 予期しない場合 fatalError("Session有効化失敗") } return true }以上でオーディオ再生時をアプリがバックグラウンドに移動しても継続できるようになります。
- 投稿日:2019-04-11T07:17:27+09:00
UIレイヤーまでユニットテストを入れているOSSアプリ/フレームワーク/ライブラリポインタ集
はじめに
UIレイヤーまでユニットテストをがっつりやっているOSSアプリ/フレームワーク/ライブラリです。
商用レベルなiOSオープンソースアプリ集と一部被りますが列挙してみました。
非UIなOSSフレームワーク/ライブラリについてはもはやテストコードがないと相手にすらされないので入れてません。
Wire iOS
検証方法:スナップショット
TwitterKit
検証方法:プロパティを検証
FireFox
検証方法:無し
Artsy
検証方法:?
Kickstarter
検証方法:MVVM、スナップショット
SpreadsheetView
検証方法:?
- 投稿日:2019-04-11T01:43:30+09:00
dockerでiOSの疑似脱獄
iOSでは、制約に縛られることがかなり多いわけですが、dockerを使って、iOSを乗っ取れるかもしれない、などと夢想しています。
たまたまiPhoneのマイク、スピーカーを共有する必要のあるプロジェクトにかかわっていまして、アイデアの一つとして、可能性を追いかけたく思っています。
皆様のご意見等いただければ幸いです。
- 投稿日:2019-04-11T01:19:35+09:00
UIGraphicsBeginImageContextを脱魔術させる
「指定色で画像を返せるようにしたいなぁ...。」
「ググろう!...スタックオーバーフローにそれっぽいの書いてあるわ!」
「これ使えるっぽいからコピペしよ!」
「使えたわ!」こんな経験ありませんか? 僕はあります。
ということで今回は、そのコピペコード/おまじないコードで何をやっているかを明らかにする。
「魔術コード1を脱魔術させる」のをテーマとして記事を書いていきます。基本的には
UIGrahics~周りのAPIが理解を妨げる原因となっているのを感じるので、その辺り突っ込んで「なぜ?」と考えていきます。1.魔術コード
今回はこのコードをベースに議論していきたいと思います。
こちらは、色とサイズからUIImageを初期化する独自定義のイニシャライザです。こちらの記事を参考にしました。
https://stackoverflow.com/questions/26542035/create-uiimage-with-solid-color-in-swift/39604716image-from-color.swiftextension UIImage { public convenience init?(color: UIColor, size: CGSize) { UIGraphicsBeginImageContext(size) let rect = CGRect(origin: CGPoint.zero, size: size) let context = UIGraphicsGetCurrentContext() context?.setFillColor(color.cgColor) context?.fill(rect) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() guard let cgImage = image?.cgImage else { return nil } self.init(cgImage: cgImage) } }2.UIGraphicsBeginImageContext?
UIGraphicsBeginImageContext(size)まずこのコードが初見では「?」です。
SwiftのAPIは、だいたい雰囲気で分かるので知らなくても調べればすぐ使えるのですが、これに関しては「画像のコンテキストを開始する?んんん?」と思いました。
APIリファレンスを見てみましょう。
Creates a bitmap-based graphics context and makes it the current context
「ビットマップベースのグラフィックコンテキストを作成し、それをカレントのコンテキストに設定します。」と書いてあります。
...まずグラフィックコンテキストって何?
3.Graphic Context?
これに関しては、Appleが出している公式ドキュメントが存在します。(アーカイブかつ、古いバージョンですがこれが分かりやすかった。
Graphics Context Basicsを読んでみたので、軽く紹介と感想を入れてきますね!(結構がっつり端折ってます!
ざっくり何するの?
Graphics Context オブジェクトの主な役割は、描画環境の現在の状態に関する情報を管理することです。
「描画環境の状態に関する情報を管理してくれるやつなのか...
」
draw(_:)周りでしてることカレントコンテキストがViewにフォーカスしてる間、パス、画像、テキスト、またはその他の必要なコンテンツを描画できます。 現在の描画環境の属性を変更して、コンテンツに必要な外観を実現することもできます。
「確かに、図形描画とかをGraphics Context使って行ってる時は、
setFillColor(:)とかして色変えたりしてた...。」
draw(_:)を抜けた後
draw(_:)メソッドがreturnした後、 Cocoaは次のビューのために描画環境をリセットするプロセスを経ます。描画環境に加えた変更をリセットし、次のビューの座標変換とクリッピング領域を設定して、独立したまっさらの環境を作ります。このプロセスはアプリケーションの各更新サイクル中に繰り返されます。「レイアウトサイクルの間、
draw(_:)の直前に該当のViewに着目して、色々描画したりして、抜けたら次のViewのためのまっさらな環境を作る!こんな感じの動きをするのか!」なるほど!!!!
...最初に出てきたカレントコンテキストって何?
4.UIGraphicsGetCurrentContext()?
let context = UIGraphicsGetCurrentContext()このコード、StackOverFlowなどでコピペコードを量産している時によく見かけるのですが、何をしているのかよくわかっていませんでした。
リファレンスの概要にはこう書いてあります。
Returns the current graphics context
どうやら、カレントのグラフィックスコンテキストを返してくれると書いてあるので、今回知りたい情報のようです。
これだけだと意味がわからないので、より詳しく書いてあるDiscussionの欄も読んでみます。
現在のグラフィックスコンテキストはデフォルトではnilです。
draw(_:)メソッドを呼び出す前に、ビューオブジェクトは有効なコンテキストをスタックにプッシュし、それを現在のものにします。 ただし、描画にUIViewオブジェクトを使用していない場合は、UIGraphicsPushContext(_:)関数を使用して手動で有効なコンテキストをスタックにプッシュする必要があります。
この関数はあなたのアプリのどのスレッドからも呼び出すことができます。draw(_:)の前に、描画環境のあれこれをstackにpushして、
UIGraphicsGetCurrentContext()では、設定された現在のコンテキストを取得できるようになっているようです。...ちなみにスタックってなんのスタック?
5.NSGraphicsContext?
グラフィックコンテキストのスタックについてハッキリ明言された記述は探すのに苦労したのですが、AppKitの中のNSGraphicsContextにそれっぽい事が書いてあります。
Graphics contexts are maintained on a stack. You push a graphics context onto the stack by sending it a saveGraphicsState() message, and pop it off the stack by sending it a restoreGraphicsState() message. By sending restoreGraphicsState() to a graphics context object you remove it from the stack, and the next graphics context on the stack becomes the current graphics context.
「グラフィックコンテキストはスタック上で維持されて、メッセージを送る事でpushとpopが可能です!保存したくなったらsaveで、復元したくなったらrestoreしてね!restore場合は、次のグラフィックコンテキストがカレントになるよ!」的な事が書かれてます。グラフィックスコンテキスト用のスタック構造が存在し、そちらで管理されてるイメージを持っています。
話を元に戻して、ここまでで見て来た内容を元に全体を整理していきます。
6.UIGraphicsBeginImageContext!
ここで最初のAPIを概要を再掲します。
ビットマップベースのグラフィックコンテキストを作成し、それをカレントのコンテキストに設定します。
スタックの話が詳しく書いてあるのが UIGraphicsBeginImageContextWithOptionsなので、そちらをみると
The drawing environment is pushed onto the graphics context stack immediately.
このコンテキストは即時にスタックにpushされるようです。つまりはカレントのコンテキストに設定されます。
これらを加味して情報を付け足して、自分なりにアレンジするとこんな感じ。
「
UIGraphicsBeginImageContextは、ビットマップベースの描画環境の現在の状態に関する情報管理オブジェクトを作成し、即時にグラフィックコンテキスト用のスタックにプッシュされます。それはカレントのコンテキストに設定されているので、UIGraphicsGetCurrentContext()で取得する事が出来ます。」おお!!!分かるやん!!!
7.ここまでの振り返り
- Graphics Contextオブジェクトの役割は、描画環境の現在の状態に関する情報を管理すること
- Graphics Contextは、それ専用のスタック構造で管理されている
- draw(_:)が呼ばれる前に、viewオブジェクトが有効なコンテキストをスタックにpushしている
- draw(_:)がリターンされた後に、描画環境リセットのプロセスが実行される
- UIGraphicsGetCurrentContext()は、スタックtopのコンテキストを取得する
- UIGraphicsBeginImageContext()は、bitmap-basedのコンテキストを作成し、即時スタックにpushしている
8.残りを駆け抜けて理解しよう
ここまで雰囲気がつかめていれば、あとは簡単なはずです。
こちらは2個目のコラムで「カレントコンテキストがViewにフォーカスしてる間、パス、画像、テキスト、またはその他の必要なコンテンツを描画できます。 現在の描画環境の属性を変更して、コンテンツに必要な外観を実現することもできます。」
と説明した内容に当たり、以下のコードではカレントコンテキストに対して塗りつぶし色を設定しています。context?.setFillColor(color.cgColor)指定領域を、コンテキストに設定してある塗りつぶし色で塗りつぶしています。
context?.fill(rect)UIGraphicsGetImageFromCurrentImageContextは、カレントのグラフィクスコンテキストがbitmap-basedの場合のみ、その情報を使って画像を返します。そうでなければnilを返します。
let image = UIGraphicsGetImageFromCurrentImageContext()最後に、
UIGraphicsBeginImageContext(_:)によって作成されたbitmap-basedのコンテキストをスタックからpopしてお掃除しています。UIGraphicsEndImageContext()この
guard let ~ elseの辺りは、class funcとして宣言していれば、return imageで済むのですが、convenience initとして定義したかったので、自身のイニシャライザを呼び出さないといけない都合の問題です。cgImageを経由しています。guard let cgImage = image?.cgImage else { return nil } self.init(cgImage: cgImage)9.脱魔術コード
ここまでで書き連ねた情報をコメントして書き加えながら、最初のコードを表示すると以下のようになります。
image-from-color.swiftextension UIImage { public convenience init?(color: UIColor, size: CGSize) { // ビットマップのグラフィックコンテキストを作成し、即時stackにpushされCurrentContextに設定する。 UIGraphicsBeginImageContext(size) // 描画領域の指定 let rect = CGRect(origin: CGPoint.zero, size: size) // stackのtopにいるグラフィックコンテキスト取得(直前で作成したビットマップのgraphicContext) let context = UIGraphicsGetCurrentContext() // グラフィックコンテキストにfillColorの設定をしている。 context?.setFillColor(color.cgColor) // 指定領域をグラフィックコンテキストの色(直上で記述した)で満たす context?.fill(rect) // Currentのグラフィックコンテキストから画像を作成する(bitmap-basedの場合) let image = UIGraphicsGetImageFromCurrentImageContext() // UIGraphicsBeginImageContextで作成したコンテキストをスタックからpop UIGraphicsEndImageContext() // 初期化処理 guard let cgImage = image?.cgImage else { return nil } self.init(cgImage: cgImage) } }どうでしょうか?雰囲気だけでも掴めたでしょうか?
自分も「CoreGraphics完全に理解した()2」ぐらいの理解度になれました。やったね!今回は、「コピペコードを理解ベースで進めていく=脱魔術」というテーマで取り組みましたが、みなさんが魔法使いを卒業できたとしたら筆者も嬉しい限りです!
10.UIGraphicsImageRenderer
ここまで長々と
UIGraphicsBeginImageContextの脱魔術化がどうこうの話をしましたが、iOS10からは使う必要はありません。クラメソ記事が詳しく解説しているのですが、以下のように書けます。
image-from-color-renderer.swiftextension UIImage { public convenience init?(color: UIColor, size: CGSize) { let renderer = UIGraphicsImageRenderer(size: size) guard let cgImage = renderer.image(actions: { rendererContext in rendererContext.cgContext.setFillColor(color.cgColor) rendererContext.fill(CGRect(origin: CGPoint.zero, size: size)) }).cgImage else { return nil } self.init(cgImage: cgImage) } }ワオ!便利!!!
明示的にコンテキストのpushとpopをさせたり、カレントコンテキスト取得のコードを書く必要がないですね!!!
直近2世代カバーをAppleが強く推奨しているのでiOS9対応しているアプリはあまりないとは思いますが、画像を作成したい場合は可能な限りこちらのAPIを利用するのが良さそうです!
11.分からなかった事
saveGState()では、GraphicsContextごとにグラフィック状態のスタックを持っていると書いてあるのですが、GraphicsContextを管理するスタックもあるので、そちらの関係が良く分からなかったです。
この辺を詳しく調べていくと、iOSやCocoaのCoreGraphicsの世界へ足を踏み入れて行くのでしょうね。
分かり次第、追記か書き直しか別記事かで対応します!
お付き合いありがとうございました!!
ここでは原理が分からない事象を魔法/魔術~と呼んでいます。 ↩























