- 投稿日:2019-11-28T23:42:19+09:00
Xcodeの複数分割状態で定義を開くウィンドウを選ぶ
Xcodeで定義にジャンプしたりウィンドウを分割して開くなど機能があり、今回複数分割に対応したことで、これらの機能がどうなったかが分かったので記事にしました。
分割して開く
option+cmd+クリック
で分割先がなければ用意し、あればそこで開きます。閉じるときはウィンドウ左上の☓ボタンで閉じれます。
更に分割を増やす
Xcodeで待ち焦がれた複数分割をサポートされました。
これはウィンドウ右上のSplitedWindowの見た目に+と入ったアイコンを押すことで増やせます。(下図参照)
またこのアイコン上で
option
を押しながらタップすることで分割先を変えることができます。(下図参照)
これを使えばたくさんのウィンドウを一度に開いた状態で開発できます。
複数画面の定義にジャンプで躓く
画面が複数ある状態で
option+cmd+クリック
しても期待する動きになりません。
これは内部的に分割した順番を持っており、次の分割先で開いています。複数画面の定義にジャンプで開く先を選ぶ
shift+option+cmd+クリック
になります。shift
を追加して定義ジャンプを試みると開くウィンドウを選べるようになります。それでは良いXcode開発ライフを。
- 投稿日:2019-11-28T22:17:19+09:00
NFC と iOS13 でホームオートメーション
iOS13 から、NFCタグをつかってホームオートメーションを設定できるようになりました。
やったこと
NFCタグに iPhone を当てたら、電気がつく or 電気が消えるようにしました。
参考記事
iPhoneをかざすだけでオートメーション発動 NFCタグとショートカットで作業効率が爆上がり
必要なもの
- iOS13 がインストールされた、iPhone -- iPhone 11, XS, XR
- NFCタグ
- 壁にNFCタグを貼るなら養生テープ
- たとえば https://a.r10.to/hzb54n
- 壁の色にあわせよう
- 壁の配線隠すのにもいいよ
- ショートカットから操作できるもの
- 電球とか philips hue
- 家電を操作できる赤外線リモコンとか Nature remo
手順
- iPhone でショートカットアプリを開く
- オートメーションタブをタップ
- 右上の「+」アイコンをタップ
- 「個人用オートメーションを作成」をタップ
- 「NFC」 をタップ
- 「NFCタグ スキャン」をタップ
- デバイスを、設定したいNFCタグにかざす
- タグに名前を付ける
- 「次へ」をタップ
- 「アクションを追加」
- 操作したいものを設定。複数可。
- 「次へ」をタップ
- 「実行の前に尋ねる」はオフにする
- 「完了」
あとは、NFCタグに iPhone をかざすとアクションが実行されるようになりますよ!
応用
アクションのところは、いろいろ応用可能です。
うちは照明やエアコンを操作したり
玄関のオートロック(オープンセサミ)を操作したりしてます。あとがき
楽しい体験をできたので、記録を残すことにしました。
はじめて Qiita の記事を書きました。
- 投稿日:2019-11-28T21:18:57+09:00
[はじめてのiOSアプリ]xcodeで地図アプリを作成(その4)
はじめに
iOSアプリを作ってみたいけど
何から始めて良いのかわからないとりあえず、
「やってみました」記事を参考に
地図アプリを真似てみようと思うという記事の4回目です。
今回は、位置情報と連携した地図表示までします。
位置情報と連動した地図表示
MapKirをインポート
- 画面左側のファイルツリーから[ViewController.swift]を選択し、画面中央に表示されるエディタで、以下のように修正
- 【なぜ?】
- 地図ライブラリ(MapKit)を使うことを宣言することで、地図表示のプログラムを記述できるようになる
ViewController.swiftimport UIKit import CoreLocation import MapKit // この行を追加 class ViewController: UIViewController, CLLocationManagerDelegate {MapKit用の変数(mapView)を追加
- 同様に[ViewController.swift]を以下のように修正
- 【なぜ?】
- この変数を通してプログラムで地図位置を取り扱うため
ViewController.swiftclass ViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet var mapView: MKMapView! // この行を追加 var locationManager: CLLocationManager!MapViewにおいて現在位置を表示するように設定
- 同様に[ViewController.swift]を以下のように修正
- 【なぜ?】
- 現在位置が表示された方が見やすいから
- 試しにtureではなくfalseを設定してみたら、透明人間みたいに表示されないから面白いかも(笑)
ViewController.swiftlocationManager.startUpdatingLocation() locationManager.requestWhenInUseAuthorization() mapView.showsUserLocation = true // この行を追加 }エディタ画面を2画面表示する
[ViewController.swift]と[Main storyboard]を同時に表示する
MapKit用の変数(mapView)とMapViewとを関連づける
テスト実行
- Xcode 左上の矢印アイコンをクリック
- Simulatorのメニューから[Debug]-[Location]-[City Run]など(移動するやつ)を選択
- 地図を拡大表示すると、地図上を移動していることを確認できる
今回の到達点
- Simulatorを使い、更新される位置情報に追従して地図上の位置(現在地)が移動するようになった
連載
- 投稿日:2019-11-28T18:43:51+09:00
Swift未経験者が1日でアプリを作った話
こんにちは、前回投稿から相当時間が空いているのでたぶん初投稿です。
なんかアプリを作ることになってしまったんですが1日でできてしまったのでそのことを書いてみます。
~前日譚~
えらいひと「iPhoneのGPSの精度わかんないし調べようよ」
ぼく「アプリつくるんですか」
えらいひと「つくっちゃお、Mac借りてくるから」
~数日後~
社内ITのひと「Mac持ってきました」
ぼく「は~い」(マジで作るんかSwiftやったことないぞ)構成図
端末からGPSの座標を取得してAzure上にデプロイしたAPIへPOSTする単純なアプリです。
いざ開発
といいつつも日ごろから触ってない環境で触ったこともない言語で一寸先は闇。困ったときはGoogle先生に頼ります。
Xcodeを入れる
Xcode入れるとiOSアプリ作れるんやね。入れたる。
プロジェクトを作る
Create a new Xcode project
っと。なにこれ…とりあえず機能多くないし
Single View App
でいいかアプリの情報適当に入れてええか。
Next
押して保存場所選択するとええな。詰む
User Interface
がSwift UI
になっててGoogleに聞けども聞けども噛み合いません。
天の神に聞いたら
「Storyboard
選べ」
とのこと。なるほど、
Storyboard
で作るとGoogleで出てくる情報通りにいくんだなと。頼るべきは神。
User Interface を Storyboard にして再度プロジェクト作成
ぼく「
Main.storyboard
がある!!!!!!!!!」
Main.storyboard
を選択するとデザイナーが出るのでUIのパーツを置いていきます。それっぽくなってきた。
コードを書く
天の声「
Assistant Editor
を開くとこのUIに紐づいたコードが表示されるらしい。」なるほど。
~10分後~
いや。丸いアイコンのボタンないじゃん。
天の声「どうやらXcodeのバージョンが新しくなっていてUIが変わってるらしい。」
viewDidLoad
が起動時に走る感じっぽい?
とりあえず書いていきます。位置情報を扱うにはまずimportを追加する必要があるとな。
import CoreLocation次に位置情報を司る
CLLocationManager
を初期化する必要があると。
コンストラクタに書くのも長くなるしメソッド化します。var locationManager : CLLocationManager! func setupLocationManager() { locationManager = CLLocationManager() guard let locationManager = locationManager else { return } // アプリがバックグラウンドでも位置情報を取りたいのでAlwaysAuthorizationを要求 locationManager.requestAlwaysAuthorization() // 端末で位置情報取得が許可されているか取得 let status = CLLocationManaager.authorizationStatus() if status == .authorizedAlways { locationManager.delegate = self locationManager.distanceFilter = 2 // 位置情報を再取得する閾値(m) locationManager.activityType = CLActivityType.automotiveNavigation // 車での移動を想定 locationManager.allowsBackgroundLocationUpdates = true // バックグラウンド取得の許可 } }こいつを起動時に呼び出してあげましょう
override func viewDidLoad() { super.vierDidLoad() setupLocationManager() }そして位置情報更新時に走る処理も作成
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let location = locations.first }あとはInfo.plistに位置情報要求時の文言を追記。
Keyに
NSLocationAlwaysUsageDescription
Valueに表示したい文言を追加っと。
あとバックグラウンドでの位置情報更新を許可しないといけなかった。
プロジェクトのSigning & Capabilities
から を入れましょう。GPSは取れたからあとはAzureに投げるだけやな!
ということで投げる部分を書きます。func sendGeoCoordinateToWebApi(_ geo: CLLocation) { let url = "https://<Azure App Service名>.azurewebsites.net/api/GeoCoordinate" let request = NSMutableURLRequest(url: URL(string: url)!) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") // jsonを作成 let params:[String:Any] = [ "Name": textOutlet.text as String?, "Latitude": geo.coordinate.latitude, "Longitude": geo.coordinate.longitude, ] // POSTする do { request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) let task:URLSessionDataTask = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) -> Void in let resultData = String(data: data!, encoding: .utf8)! print(resultData) }) task.resume() } catch { print("Error: \(error)") return } }jsonを作成の部分では以下のようなjsonを作っています。作ったAPIと形式を合わせてるだけなので適当です。
{ "Name": "ワイ", "Latitude": 111, "Longitude": 111, }
textOutlet
はtextboxから値を引いてくるため設定したアウトレットです。
あとはこれを一情報更新時に発火するメソッドから呼び出すだけやな!func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let location = locations.first sendGeoCoordinateToWebApi(location) }完成!
早速AzureのSQL DBの中を見てみます。
値が入ってる~~~~~~~~~~~!!!!!!!!!!!!!!!!!!!!!!
というわけで1日(1営業日)でアプリ(と値を受け取るAPI)ができちゃいました。
おつかれさまでした。おわり。
- 投稿日:2019-11-28T17:02:50+09:00
UXデザイナーと連携する際に考慮したい事項
1ピクセルのデザインにこだわるUXデザイナーとの連携で、いろいろ苦労した経験からの知見を簡易的にまとめます
ピクセル単位での調整を可能にする
エンジニアが、都度、確認できるデザインスペックが必要
XD、Sketch、Prott、Abstractなど、ピクセル単位で確認でき、素材を書き出せるツール・サービスを使う要件定義書
最初、エクセルやスプレッドシートを使っていたが、エンジニアは更新しやすいものの、デザイナーや企画者は使いづらい
さらに、デザインスペックを別で用意する必要がある
→ XDで要件定義を作成してもらい、デザインスペックも同時に作成&確認できるようにしたワイヤーフレーム
要件定義からデザインを起こすまでのステップで、デザイナーがどのレベルまでデザインに落とし込めばいいか分からない
→ アプリエンジニアは、要件とデザインの両方がないと、実現可能かを判断できないケースが多いので、ある程度のデザインは必要だが、完成版を見せられると、その時点で判断不能に陥ることが多いので、ワイヤーフレームレベルのデザインで十分。
デザイナーとエンジニアで話し合うステップが必要なので、ワイヤーフレームレベルのデザインで一旦、エンジニアに共有するデザインスペックの指示通りに、修正は結構な手間がかかる
デザイナーがXcodeを使えるようにして、文字サイズやフォント、ピクセル単位での修正は、デザイナーにやってもらう
デザイナーから受け取った素材を、リネームする手間がかかる
スプレッドシートで、素材依頼シートを作って、名前を指定して書き出してもらう
※あまりいい運用じゃなかったので、もっといい方法を考えた方がいいピクセル単位で調整していると同じような素材が増え、アプリサイズが肥大化する
共通で使う素材は、命名規則で判別しやすいようにする。common_XXXXX など
デザイナーがエンジニアに渡す際に、共通で使う素材かどうかを伝えてもらえると助かります。カラーが細かくなり、どのカラーを使えばいいか、迷ってしまう
基本は、デザインスペック通りだが、間違えてるのではと思う箇所が出がちなので、大まかに、テキスト用のカラーとか、テーマカラーとかを決めて、間違いに気づけるように
iOSのTips
- 行間は、TextFieldだと行間指定できないので、UILabelかTextViewに変更する必要がある
- フォントを、システムフォントじゃなくて、ヒラギノに明示的に設定する
AndroidのTips
- Android は、dpで指定 (端末のフォントサイズ設定に対応する場合は、spがいいが、フォントサイズ対応すると、レイアウト崩れを考慮しないといけないので、逆にdp指定で対応させない)デザインのベースにする解像度の決め方
各プラットフォームでの端末シェアを考慮
【2019最新】スマホ・タブレットの解像度一覧表(画面サイズの割合)
https://webdesign-abc.com/tech/resolution-list/【2019年】最新のディスプレイ・モニター解像度シェアが知りたい!
https://web-knight.net/display-resolution/「iPhone6/7/8」で解像度は「375 * 667」
「Android XPERIA」。解像度は「360 x 640」
が無難デザイナーにお願いしたいこと
Atomic Design を考慮して、パーツ・コンポーネント単位で定義してほしい→
これが出来ると、開発でもコンポーネント化しやすい
[参考]
Atomic Design を分かったつもりになる
https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/AppleのHIGを隅々まで熟読してほしい
Human Interface Guidelines
https://developer.apple.com/design/human-interface-guidelines/
HIGから学ぶ、3つのナビゲーションとそのあるべき姿とは
https://note.com/ta0o_o0821/n/n140cd75cc023HIGを理解したあと、iOSとAndroidアプリの違いや、マテリアルデザインを熟読
マテリアル デザインでアプリの魅力を高める
https://developer.android.com/distribute/best-practices/develop/use-material-design?hl=JA
ただ、個人的には、マテリアルデザインのリファレンスは、より具体的な実装方法のマニュアル的な部分が多く記事も多いので、熟読するというより、マテリアルデザインがどういうもので、どんな事ができるかを把握してほしいという感じ
Human Interface Guidelines は概念的な内容で、マテリアルデザインはデザイン手法のマニュアルといった感じその他
iOSとAndroidのUIの違い
https://nogson2.hatenablog.com/entry/2018/11/16/123256iOS、Androidの解像度について(iOSはpt / Androidはdp)
https://qiita.com/eKushida/items/ff1ecaacdb54ce7f9f5cエンジニアもデザインのことを知る必要がある
【これからのスキル】デザイナーとエンジニアの境界線がどんどん無くなる
https://blog.btrax.com/jp/designer-engineer/
優れたプロダクト開発のために。エンジニアがデザインを学ぶ時のおすすめ本
https://goodpatch.com/blog/designbooks-for-developer/
- 投稿日:2019-11-28T16:12:00+09:00
no app store connect access for the teamの対処法
概要
archive後、validateやdistribute時に「no app store connect access for the team」と出てきて
苦戦したので対策を書いておきます。
知りたいのは結果だと思うので成功した対処法は先に書いておきます。成功した対処法
- 再起動。 PCかXcodeを再起動するとエラーが解決できたので、この問題にぶつかった時はぜひやってみてください。
試したこと
- app storeの識別子、認証、プロファイルなど
- archiveの右下にあるDetailsのTeamを入力し直す
参考(他の参考になりそうな投稿を載せておきます)
No App Store connect access for the team 対策
アプリをArchiveするときに No accounts with iTunes Connect access というエラーが出てアップロードできない時
- 投稿日:2019-11-28T10:12:03+09:00
iOSのいろいろなアーキテクチャパターンを試してみる
はじめに
iOSのアーキテクチャパターンに言及する記事はすでにQiita上のみならず、ネット上にたくさん存在しています。
それらの記事は大変わかりやすく、読んだ直後はわかった気になるのですが、学んだアーキテクチャをいざ実際に使おうとなると途端になにもできなくなり、ほとんど理解していなかったことに気づくのです…。
そこで今回は、iOSの設計パターンについてきちんと勉強しなおし、備忘録とするべく、その内容を自分なりの言葉と簡単なサンプルコードでまとめてみようと思います。
本記事で取り扱う設計パターン
- MVC
- MVP
- MVVM
- Flux
作ったものとソースコード
今回作るのはごく簡単なGitHubのクライアントアプリ。GitHub APIを使い検索ワードに応じてRepositoryを一覧表示します。TableViewのCellをタップするとSafariViewControllerを使って内容を詳細表示する仕様としました。
https://github.com/TatsuhiroAbe/iOSDesignPatterns
アーキテクチャごとにブランチを用意しています。アーキテクチャパターンを学ぶ前に
勉強を進めていくなかで、「そもそもなぜアーキテクチャパターンを開発したり利用したりするのか」をきちんと認識することがかなり重要だと感じました。
一言で言ってしまえば、責務を適切に分離するためということになるでしょうか。
ソフトウェア開発を行なっていると、開発が進むにつれて機能が増えていき、対処すべき問題がどんどん大きくなってしまいます。そこで、それらの問題をより小さな単位に分離して、出来るだけ単一の責務に向かわせようとするのが一般的ですよね。アーキテクチャパターンというのは、その責務の切り分けを適切に行うための、文字通り「パターン」です。対処すべき問題はソフトウェアごとに違えど、その本質的な部分は多くの場合共通しています。そのような多くの場面で直面しうる責務の切り分け方をパターン化してくれたのが「アーキテクチャパターン」です。
特にiOSのアーキテクチャパターンにおいては、Presentation Domain Separation(PDS)というアイデアが基本になっているようです。
Presentationとは「UIに関するロジック」であり、Domainは「システム本来の関心領域」を指します。これらはそれぞれMV*系のアーキテクチャにおけるViewとModelに相当する部分ですね。
私も含め「結局よく分からない」という人は、「責務を適切に分離するため」、特に「UIに関するロジックとシステム本来のロジックを分離するため」という目的を意識しながらそれぞれのアーキテクチャパターンに向き合うと、やや理解が容易になるのかもしれません。
MVC
まず最初は、今回取り上げるアーキテクチャの中でもおそらく一番有名なMVC。iOSアプリに限らず、多くのWebフレームワークでも採用されているアーキテクチャです。
iOSアプリで利用されるMVCは厳密にはCocoa MVCと呼ぶそうで、よく下のような図とともに説明されています。
(https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.htmlから引用)前述のPDSによると、View(UIに関するロジック)とModel(ビジネスロジック)を分離することが基本的な目的になります。MVCにおいてはControllerがViewとModelを参照し、両者の仲介役となることでそれを実現します。
このような設計であることから、Controllerの部分はどうしてもいろいろな処理が集中しがちで、いわゆるFarViewControllerになりやすいです。「MVCはMassive View Controllerのことだ」という皮肉交じりの表現はかなり有名ですね。
実装上のポイントは、Modelが自らの状態更新をControllerへ通知する部分(図中の"Notify"の部分)だと思います。イベント通知の手段として、SwiftではDelegate、クロージャ、NotificationCenterなどを使うことが多いですが、今回はDelegateパターンによって実装しました。
RepositoryModel.swiftprotocol RepositoryModelDelegate: class { func repositoryModel(_ repositoryModel: RepositoryModel, didChange repositories: [Repository]) } class RepositoryModel { let BASE_URL = "https://api.github.com/search/repositories?q=" weak var delegate: RepositoryModelDelegate? private(set) var repositories: [Repository] = [] { didSet { delegate?.repositoryModel(self, didChange: repositories) } } func fetchRepositories(_ query: String) { let url = URL(string: BASE_URL + query)! let task = URLSession.shared.dataTask(with: url) { (data, response, error) in guard let data = data else { return } if let error = error { print("error: \(error.localizedDescription)") return } do { let repositoriesList = try JSONDecoder().decode(RepositoriesList.self, from: data) self.repositories = repositoriesList.repositories } catch let err { print("decode error: \(err.localizedDescription)") return } } task.resume() } }MVP
MVPはModel、View、Presenterという3つのレイヤーから成るアーキテクチャです。それぞれの役割はこんな感じ
- Model:他のアーキテクチャにおけるModelと同じ。ビジネスロジックを担うレイヤー。
- View:ViewControllerも含めたUIViewのサブクラス。UIイベントを受け付け、Presenterに伝える。
- Presenter:ViewとModelの仲介役。Viewからの入力を受け、Modelにコマンドを送る。Modelからの状態更新を受け取り、Viewを更新。Viewに表示するためのデータを保持するのもここ。
ViewとModelを完全に分離したいという目的はMVCと共通ですが、MVPではさらに描画処理とプレゼンテーションロジックを分離するという目的があります。
ここで言う「描画処理」というのは、
label.text = newText
やtableView.reloadData()
という文字通り描画のための処理のことで、「プレゼンテーションロジック」というのは、ビューの更新やページ遷移(どの内容を表示するのかを決定する処理)などのUIのビジネスロジックを指します。MVCではViewControllerにまとめらていたこれら2つの処理をViewとPresenterにそれぞれ分離することで、FatViewControllerを避けられるということです。
以下に示すのはサンプルのPresenterの実装です。
RepositoryViewPresenter.swiftprotocol RepositoryPresenter: class { var view: RepositoryView? { get set } var numberOfRepositories: Int { get } func repository(_ row: Int) -> Repository? func didSelectRow(at indexPath: IndexPath) func didTapSearchButton(with searchText: String?) } class RepositoryViewPresenter: RepositoryPresenter { weak var view: RepositoryView? private(set) var repositories: [Repository] = [] private var model: RepositoryModelProtocol! init(model: RepositoryModelProtocol = RepositoryModel()) { self.model = model } var numberOfRepositories: Int { return repositories.count } func repository(_ row: Int) -> Repository? { guard row < repositories.count else { return nil } return repositories[row] } func didSelectRow(at indexPath: IndexPath) { guard let repository = repository(indexPath.row) else { return } view?.showSafariView(repository.url) } func didTapSearchButton(with searchText: String?) { guard let searchText = searchText, !searchText.isEmpty else { return } model.fetchRepositories(searchText) { [weak self] result in switch result { case let .success(repositories): self?.repositories = repositories DispatchQueue.main.async { self?.view?.reloadData() } case let .failure(error): print(error) } } } }ご覧にように、表示するリポジトリ一覧である
repositories
を保持するのもこの層の役割です。また、セルの選択や検索ボタンの押下に応じて、該当するリポジトリを返したり、Modelを呼び出してリポジトリ一覧の内容を更新したりするプレゼンテーションロジックを処理します。
MVVM
MVVMは、以下の3つのレイヤーから成るアーキテクチャです。
- Model:他のアーキテクチャにおけるModelと同じ。ビジネスロジックを担うレイヤー。
- View:UIイベントを受け付けてViewModelに伝える。ViewModelを監視し、その状態更新を受けて描画処理を行う。
- ViewModel:Viewからの入力を受け、Modelの処理を呼び出す。Modelからの状態更新を受け取り、自身の状態を更新。Viewに表示するためのデータを保持する。
Viewの説明にある「ViewModelを監視し、その状態更新を受けて描画処理を行う」という処理を可能にするのが、データバインディングと呼ばれる仕組みです。
以下がViewModelの状態更新を受けてViewを更新するために、データバインディングを行なっている部分。
RepositoryViewController.swiftclass RepositoryViewController: UIViewController { override func viewDidLoad() { // 省略 viewModel.repositories .bind(to: tableView.rx.items(cellIdentifier: "RepositoryCell")) { (_, repository, cell: RepositoryCell) in cell.configure(repository) } .disposed(by: disposeBag) viewModel.deselectRow .bind(to: Binder(self) { viewController, indexPath in viewController.tableView.deselectRow(at: indexPath, animated: true) }) .disposed(by: disposeBag) viewModel.openURL .bind(to: Binder(self) { viewController, url in let safariViewController = SFSafariViewController(url: url) viewController.present(safariViewController, animated: true, completion: nil) }) .disposed(by: disposeBag) } }ViewControllerには、ViewとViewModelのデータバインディングのみを書くことになるので、だいぶスッキリする印象ですね。
ちなみに、上記の実装でもそうですが、iOSでMVVMというと、ほぼ確実にRxSwift(とRxCocoa)がセットで話題に上がります。
MVVM = RxSwiftというわけでは決してないのですが、MVVMをシンプルに記述するためにも、ほとんどの場合でRxSwiftが導入されるのが現状です。(そして大概RxSwiftの学習コストの高さがデメリットとして指摘されます。)MVVMは、アーキテクチャ自体に関する理解に加え、RxSwiftの理解とその背後にあるObserverパターンの理解を求められるという点で、広く使われてる割には導入コストが高いアーキテクチャパターンだと感じました。
Flux
(https://facebook.github.io/flux/docs/in-depth-overview/から引用)
- Action:実行する処理を特定するためのtype(例:"update_repository"のようなラベル)と、それに関連するdata(例:update後の値)がセットになったもの([type : data])。
- Dispatcher:Actionを受け取り、その値をStoreに伝える。
- Store:Dispatcherから伝わってきたActionを受けて、自身の状態を更新して通知。
- View:Storeの状態を監視し、その状態更新を受けて画面を更新。
Fluxアーキテクチャの特徴は「単一方向のデータフロー」という考え方です。
Fluxでは、常にView→Action→Dispatcher→Store→Viewという一方向のデータフローが生成されます。このことのメリットは、アプリケーションが複雑化したり、それに伴って開発者が増えたとしてもデータフローが追いやすく、保守性の高いコード書けることだそうです。
今回作ったアプリは機能的にすごくシンプルなものだったため、このメリットはほとんど享受できていません。そればかりか、慣れない複雑な設計を用いたために、実装しづらかったというのが正直な感想です。
エンジニアであれば、「よりスケールしやすい新しくて重厚なアーキテクチャパターンを使いたい」という気持ちは多かれ少なかれあるでしょうが、アーキテクチャパターンの選定においては、開発するアプリの性質やメンバーの習熟度なども考慮しなければならないと考えると難しいところですね。
実装に関しては、こちらのコードを参考にさせていただきました。
感想
一言で感想を述べると、「やっぱり設計は難しい!!」ということにつきますね。
今回取り上げたものの他に、ReduxやClean Architecture, VIPERなども今では主要なアーキテクチャパターンとして知られています。
アーキテクチャパターンを勉強しようとする際の大きな動機の1つとして、「開発するソフトウェアの性質に応じた適切な設計ができるようになりたい」というものがあるでしょう。
私も例外ではなくそう思っていたのですが、主なものだけでもこれだけあるアーキテクチャパターンを十分に理解し、実装できるだけの技術力を身につけ、その上で適切な設計を行えるようにするというのは実際かなり険しい道のりになりそうです…
参考
- 投稿日:2019-11-28T02:31:58+09:00
【iOS】Flutterで環境ごとに切り替えビルドする(debug/stg/prod)
プロダクトで開発をしていると、検証等で環境ごとにビルドしたくなる事があると思います。
今回はFlutterのflavorというオプションを使って、
- Debug → 開発環境(Debugビルド)
- Staging → ステージング環境(Debugビルド, Releaseビルド)
- Production → 本番環境(Releaseビルド)
の3つの環境(ビルド構成は4つ)に分けてビルドする方法をまとめていきます。
下準備
環境構築
※Flutterの環境構築がまだだよって方がこちらをどうぞ
FlutterのStableチャンネルを利用してます。
※ 2019/11/28 現在で1.9.1+hotfix.6チャンネル切り替えは
flutter channel
でできます。$ flutter channel stable
flutter --version
を実行してみて以下と同じ感じになればOKです!$ flutter --version Flutter 1.9.1+hotfix.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision 68587a0916 (2 months ago) • 2019-09-13 19:46:58 -0700 Engine • revision b863200c37 Tools • Dart 2.5.0※ もし実行時のStableチャンネルではうまく行かないよという場合は
flutter version
でversionを指定できるので試してみてください。プロジェクト生成
flutter create
でFlutterプロジェクトを生成します。$ flutter create \ --org me.skycat.example.flutter.env \ --with-driver-test \ flutter_env_build※IDEでFlutterProjectを作成してもOKです
プロジェクトが完成したら
flutter run
してみて、初期のカウンターアプリが動くか確認しましょう!iOSで各環境のビルド構成を設定する
Xcode上で各環境のSchemaを作り、xcconfigで各環境ごとにビルド設定をしましょう。
Xcodeプロジェクトを編集する
プロジェクト直下のiosディレクトリ内の
Runner.xcodepro
jを開いて編集してください。(ios/Runner.xcodeproj
)デフォルトのビルド環境は以下の画像ように、Debug/Release/Profleの3つのビルド構成があり、DebugはDebug.xcconfig、ReleaseとProfileはRelease.xcconfigが設定されています。
必要なビルド構成を追加する
今回はDebug/Debug-Staging/Staging/Productionの4つの構成にしたいので、まだないDebug-Staging/Staging/Productionを追加していきましょう!
※ Debug-StagingはStagingのDebugビルド構成です。
[+]
を押して、すでにある構成をコピーして作りましょう。
Duplicate "Release" Configuration
をクリックして作ります。
作成後はダブルクリックで名前を変更できるので適切な名前(Debug-Staging/Staging/Production)に変更してください。
追加が終わったら、不要なRelease/Profleを消しましょう!
以下の画像のようになっていたら完璧です。
それぞれの環境用のビルド設定(xcconfig)を追加する
ビルド構成ごとにxcconfigを用意しましょう。
プロジェクト直下のios/Flutterに以下のファイルを追加してください。Debug.xcconfig
Debug.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Debug PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.debug DISPLAY_NAME=SAMPLE-debugStaging.xcconfig
Staging.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Staging PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.stg DISPLAY_NAME=SAMPLE-stgProduction.xcconfig
Production.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Production PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.prod DISPLAY_NAME=SAMPLE-prod追加したものをビルド構成に設定していきます。
※ 選択肢に追加したものが出てきていない場合は、Xcodeに認識されないので再追加の必要があります。ビルド設定(xcconfig)に設定した変数を適用
xcconfigで設定した変数を適用させるためにプロジェクトの設定を変更する必要があります。
アプリの表示名(DISPLAY_NAME)を適用させるために、
info.plist`に
CFBundleDisplayNameを
$(DISPLAY_NAME)`として定義してください。
<key>CFBundleDisplayName</key> <string>$(DISPLAY_NAME)</string>そしてBundleIDが反映されるように、TARGETS->Runner->IdentityのBundle Identidierを
--PRODUCT-BUNDLE-IDENTIFIER-
に設定してください。
Flavorに対応したSchemaを追加
Flutterのflavorオプションからビルド構成を選択するためにSchemaを3つ追加します。
Product > Schema > Edit Schema
Ducplicate Schema
をクリックするとコピーとして作られるので、Schema名をビルド構成と同じ名前に設定します。
そして
Run
のinfoタブ
を開いて、Build Configuration
にSchema名と同じビルド構成を選択してください。
StagingとProductionは*Debug executable*のチェックを外してください
Debug/Staging/Productionの3つ分が作り終わるとこんな感じになります。
Product > Schema > Schema Manager
これでビルドの準備ができました!Flavorを指定してビルドしてみましょう!
Flavorが未設定(flutter run)だと表示名が SAMPLE-debug BundleIDが me.skycat.example.flutter.env.debugFlavorがStaging(flutter run --flavor Staging)だと
表示名が SAMPLE-stg BundleIDが me.skycat.example.flutter.env.stgなアプリがインストールされます!
これでiOSのFlavor設定はおわりです!
※ 注意 ※
ProductionはDebugモードの設定いれていないので、Simulationでは実行できません!
(本番環境をSimulationで見ることなんてないやろという考え)おわりに
「うまくいかない」とか「ここってどうなの?」みたいなことがあったらコメントお願いします?
Androidはまた別記事で書きます〜。そのあとくらいに、
- いろんなCI(Codemagic, Github Actions, Bitrise...etc)での設定
- Flavorでの各種切り替え(Firebaseプロジェクト, ランチャーアイコン, APIの向き先)
などなどの記事も書いていきます〜。
- 投稿日:2019-11-28T02:31:58+09:00
【iOS】FlutterでFlavorを使って環境ごとに切り替えてビルドする(debug/stg/prod)
2019/11/28 13:12
AndroidのFlavorと合わせるために一部Flavor名を修正しました(Debug→Develop)[
その他、細かい修正プロダクトで開発をしていると、検証等で環境ごとにビルドしたくなる事があると思います。
今回は Flutter の flavor というオプションを使って、
- Develop → 開発環境(Debug ビルド)
- Staging → ステージング環境(Debug ビルド, Release ビルド)
- Production → 本番環境(Release ビルド)
の 3 つの環境(ビルド構成は 4 つ)に分けてビルドする方法をまとめていきます。
下準備
環境構築
※Flutter の環境構築がまだだよって方がこちらをどうぞ
Flutter の Stable チャンネルを利用してます。
※ 2019/11/28 現在で 1.9.1+hotfix.6チャンネル切り替えは
flutter channel
でできます。$ flutter channel stable
flutter --version
を実行してみて以下と同じ感じになれば OK です!$ flutter --version Flutter 1.9.1+hotfix.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision 68587a0916 (2 months ago) • 2019-09-13 19:46:58 -0700 Engine • revision b863200c37 Tools • Dart 2.5.0※ もし実行時の Stable チャンネルではうまく行かないよという場合は
flutter version
で version を指定できるので試してみてください。プロジェクト生成
flutter create
で Flutter プロジェクトを生成します。$ flutter create \ --org me.skycat.example.flutter.env \ --with-driver-test \ flutter_env_build※IDE で FlutterProject を作成しても OK です
プロジェクトが完成したら`
lutter run
してみて、初期のカウンターアプリが動くか確認しましょう!iOS で各環境のビルド構成を設定する
Xcode 上で各環境の Schema を作り、xcconfig で各環境ごとにビルド設定をしましょう。
Xcode プロジェクトを編集する
プロジェクト直下の ios ディレクトリ内の
Runner.xcodepro
を開いて編集してください。(ios/Runner.xcodeproj
)デフォルトのビルド環境は以下の画像ように、Debug/Release/Profle の 3 つのビルド構成があり、
Debug は Debug.xcconfig、Release と Profile は Release.xcconfig が設定されています。
必要なビルド構成を追加する
今回は 以下の4つの構成を作ります。
- Debug-Develop
- DevelopのDebugビルド構成
- Debug-Staging
- StagingのDebugビルド構成
- Release-Staging
- StagingのReleaseビルド構成
- Release-Production
- ProductionのReleaseビルド構成
[+]
を押して、すでにある構成をコピーして作りましょう。
Duplicate "Release" Configuration
をクリックして作ります。
作成後はダブルクリックで名前を変更できるので適切な名前に変更してください。追加が終わったら、不要な Release/Profle を消しましょう!
以下の画像のようになっていたら完璧です。
それぞれの環境用のビルド設定(xcconfig)を追加する
ビルド構成ごとに xcconfig を用意しましょう。
プロジェクト直下の ios/Flutter に以下のファイルを追加してください。Develop.xcconfig
Develop.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Develop PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.dev DISPLAY_NAME=SAMPLE-devStaging.xcconfig
Staging.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Staging PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.stg DISPLAY_NAME=SAMPLE-stgProduction.xcconfig
Production.xcconfig#include "Generated.xcconfig" TRACK_WIDGET_CREATION= FLUTTER_FLAVOR=Production PRODUCT_BUNDLE_IDENTIFIER=me.skycat.example.flutter.env.prod DISPLAY_NAME=SAMPLE-prod追加したものをビルド構成に設定していきます。
※ 選択肢に追加したものが出てきていない場合は、Xcode に認識されないので再追加の必要があります。ビルド設定(xcconfig)に設定した変数を適用
xcconfig で設定した変数を適用させるためにプロジェクトの設定を変更する必要があります。
アプリの表示名(DISPLAY_NAME)を適用させるために、
info.plist
にCFBundleDisplayName
を$(DISPLAY_NAME)
として定義してください。 <key>CFBundleDisplayName</key> <string>$(DISPLAY_NAME)</string>そして BundleID が反映されるように、
TARGETS->Runner->BuildSettings のProduct Bundle Identifier
を削除(フォーカスした状態でDeleteKeyでOK)して、
info.plistのCFBundleIdentifier(.xcconfigで設定しているPRODUCT_BUNDLE_IDENTIFIER)を読み込むようにします。
Flavor に対応した Schema を追加
Flutter の flavor オプションからビルド構成を選択するために Schema を 3 つ追加します。
Product > Schema > Edit Schema
Ducplicate Schema
をクリックするとコピーとして作られるので、Schema 名をビルド構成と同じ名前に設定します。
そして
Runのinfoタブ
を開いて、Build Configuration
に Schema 名と同じビルド構成を選択してください。
StagingとProductionは*Debug executable*のチェックを外してください
(Debug表示がなくなってログが吐かれなくなります、必要な人は外さなくてもいいです)ReleaseビルドのあるStagingとProductionは
Profile
とArchives
もBuild Configuration
設定してください。
StagingはRelease-Staging
、ProductionはRelease-Production
になります、Develop/Staging/Production の 3 つ分が作り終わるとこんな感じになります。
Product > Schema > Schema Manager
これでビルドの準備ができました!
Flavor を指定してビルドしてみましょう!Flavor が Staging(flutter run --flavor Develop)だと
表示名が SAMPLE-dev BundleIDが me.skycat.example.flutter.env.devFlavor が Staging(flutter run --flavor Staging)だと
表示名が SAMPLE-stg BundleIDが me.skycat.example.flutter.env.stgなアプリがインストールされます!
これで iOS の Flavor 設定はおわりです!
※ 注意 ※
Production は Debug モードの設定いれていないので、Simulation では実行できません!
(本番環境を Simulation で見ることなんてないやろという考え)おわりに
「うまくいかない」とか「ここってどうなの?」みたいなことがあったらコメントお願いします ?
Android はまた別記事で書きます〜。そのあとくらいに、
いろんな CI(Codemagic, Github Actions, Bitrise...etc)での設定
Flavor での各種切り替え(Firebase プロジェクト, ランチャーアイコン, API の向き先)
などなどの記事も書いていきます〜。よろしくおねがいします?
- 投稿日:2019-11-28T01:27:48+09:00
Objective-C Bridging Headerファイルを編集したら大量のビルドエラーが出る時の対処
なにが起きたか
Objective-C Bridging Headerファイル(
Objective-C-Bridging-Header.h
とか)を編集した後にビルドしたら大量のビルドエラーが発生した
対処
エラーが出てるファイルに
import UIKit
を追記する
無理でしょ…Objective-C Bridging Headerファイルに
#import <UIKit/UIKit.h>
を追記する
※NSObject
などのエラーが出ている場合は#import <Foundation/Foundation.h>
を追記する参考
https://stackoverflow.com/questions/26116288/failed-to-import-bridging-header
UIKitとかFoundationがなくても、ライブラリが機能するようにということらしい。