- 投稿日: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-28T19:54:29+09:00
ログイン時などに開かれる Safari をフルスクリーンにする。例:Googleログイン
はじめに
iOSエンジニアのやまたつです!
iOS13で modalPresentationStyle のデフォルトが変更されたため、modalPresentationStyle = .fullScreen
を付加していく作業を行っています。(画面数多いとつらい)ログインに GIDSignIn を使用していたときに、「どこに .fullScreen 書くんだろう?」となったので解決策のシェアです。
解決策
SFSafariViewController でログイン画面を表示させるもの限定になりますが、
SFSarafiViewController を拡張するとOKです。SFSafariViewController+.swiftextension SFSafariViewController { override open var modalPresentationStyle: UIModalPresentationStyle { get { return .fullScreen} set { super.modalPresentationStyle = newValue } } }さいごに
SFSafariViewController は .fullScreen にせずにハーフモーダルで良いと思ってます。笑
- 投稿日: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-28T16:36:20+09:00
NISAの積み立てシュミレーションをざっくり書いてみた
概要
複利計算が電卓でやると面倒なので、ざっくり書いてみました。
細かいものは省いているのでご容赦ください。参考
コード
//Swift var year = 0 //現在の継続年数を示す var targetYear = 30 //継続期間 var beforeMonthlyPrincipal: Double = 33000 //毎月投入金額 var afterMonthlyPrincipal = 100000.0 //税免除後の毎月投入金額 var beforeInterestRate = 1.08 //免除前金利 var afterInterestRate = 0.08 //免除後金利 var assets: Double = 0 //資産 var devidedAmountOfMoney: Double = 0 //配当金額 var realIncome: Double = 0 //実収入 var incomeTax = 0.8 //20% //税免除時 while year < 20{ assets += beforeMonthlyPrincipal*12 assets *= beforeInterestRate year += 1 print("\(year)年: \(Int(assets))円") } print("税免除終了時の資産額: \(Int(assets))円") print("税免除終了時の投入金額: \(Int(Double(year)*(beforeMonthlyPrincipal*12)))円") //税免除終了時 while year < targetYear { assets += afterMonthlyPrincipal*12 devidedAmountOfMoney = afterInterestRate * assets assets += devidedAmountOfMoney * incomeTax year += 1 } print("\(year)年後の資産額: \(Int(assets))円") print("\(year)年後の投入金額: \(Int(Double(10)*afterMonthlyPrincipal*12) + Int((Double(year)*(beforeMonthlyPrincipal*12))))円")GitHub
- 投稿日:2019-11-28T14:57:33+09:00
スネークケースをキャメルケースに変換する方法
みなさんこんにちは!!
この記事ではSwiftにおいてスネークケースをキャメルケースに変換する方法について解説していきます!!
(初めての本格的な技術的な記事なので稚拙な文章、知識の浅さがありますがお許しください!というかご指摘ください)今回の目的
先人の方が投稿している記事にはなぜ理解しておかなければならないのか?どういったところで活用するのかが初学者の私には難しかった
そのため理解していなかった昔の自分に説明するつもり噛み砕いて説明しようと思ったからです!(考えを整理するためでもあります!)そもそもの話(スネークケース、キャメルケースとは??)
キャメルケース:簡単にいうとSwiftで推奨されている形です
CamelController.swiftfinal class ViewController { private func camelSample() { print("ラクダ") }このcamelSampleをみて感じることがありませんか。。。?
そうなんです。。。凸凹しているでしょ????
だんだん見えてくるんですラクダに!!
つまりラクダみたいな塊=キャメルケースなのですスネークケース:JSONとかでよく返ってくる奴ら
Sample.JSON{ first_name: "蛇塚" last_name: "太郎" }APIを叩いてる時などに目にする形です。。。
先ほどのキャメルケースと違う点はどこでしょうか??
単語のつなぎ目が大文字ではなく_で繋がれています!
もう見えてきますよね!!!
_って蛇なんです
つまりは単語の区切り目を_でつないでいる=スネークケースなのですSwiftにおけるスネークケースの取り扱いかたについて(CodingKey)
ざっくりとキャメル、スネークケースについて理解ができたところでどんな場面で扱うことのかを解説していきます!
先ほどのデータをCodableを使用して表示することを考えていきますと。。。
SampleResponse.swiftstruct User: Codable { let first_name: String, let last_name: String }と初学者のみなさんは直感的に書くと思います(実際に私がそうでした)
しかしこれで書いてしまうと大きな問題が存在します。。。
それはコード上にキャメルケースとスネークケースが混在する危険があるということです!
Swiftではキャメルケースが推奨されているのでスネークケースがコード上に介入してくのはよろしくありません
(スキーに海パンでいくようなものです。。。)気持ちが悪いですよね。。。
キャメルケースとスネークケースが混在してしましましたこれを解消するのがCodingKeyです!!
SampleResponse.swiftstruct User: Codable { let firstName: String, let lastName: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" } }これによりスネークケースをキャメルケースにすることができました!!
特定の値を取り出す時に自らがcaseで設定したもの命名から取り出すことができます!
→コード状にスネークケースが介入するのを防げました最後に
今回は概要を理解するための簡単な内容でした。。。
なぜそのままJSONの名称を使うのがよろしくないかについては名称が縛られてしまうためです。。。
→名称は解読性に大きな影響を与えてします→x自分で考えてつけたいですよね!!
まだまだ本来は奥が深いものですが先人様達の記事を参考にして勉強していきます!今回は概要だけ、さらには稚拙な文章でしたが役に立つことができれば幸いです
次回の記事は本格的なAlamofierの使い方について解説していきます!!(今回の具体的なコードも掲載する予定です)
質問点やご指摘があればどしどし絡んできてください(虐めないでくださいね。。。)
- 投稿日: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-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がなくても、ライブラリが機能するようにということらしい。