20191128のSwiftに関する記事は7件です。

[はじめてのiOSアプリ]xcodeで地図アプリを作成(その4)

はじめに

iOSアプリを作ってみたいけど
何から始めて良いのかわからない

とりあえず、
「やってみました」記事を参考に
地図アプリを真似てみようと思う

という記事の4回目です。

今回は、位置情報と連携した地図表示までします。

位置情報と連動した地図表示

  1. MapKirをインポート

    • 画面左側のファイルツリーから[ViewController.swift]を選択し、画面中央に表示されるエディタで、以下のように修正
    • 【なぜ?】
      • 地図ライブラリ(MapKit)を使うことを宣言することで、地図表示のプログラムを記述できるようになる
    ViewController.swift
    import UIKit
    import CoreLocation
    import MapKit  // この行を追加
    
    class ViewController: UIViewController, CLLocationManagerDelegate {
    
  2. MapKit用の変数(mapView)を追加

    • 同様に[ViewController.swift]を以下のように修正
    • 【なぜ?】
      • この変数を通してプログラムで地図位置を取り扱うため
    ViewController.swift
    class ViewController: UIViewController, CLLocationManagerDelegate {
        @IBOutlet var mapView: MKMapView!  // この行を追加
        var locationManager: CLLocationManager!
    
  3. MapViewにおいて現在位置を表示するように設定

    • 同様に[ViewController.swift]を以下のように修正
    • 【なぜ?】
      • 現在位置が表示された方が見やすいから
      • 試しにtureではなくfalseを設定してみたら、透明人間みたいに表示されないから面白いかも(笑)
    ViewController.swift
        locationManager.startUpdatingLocation()
        locationManager.requestWhenInUseAuthorization()
    
        mapView.showsUserLocation = true  // この行を追加
    }
    
  4. エディタ画面を2画面表示する

    • 画面左側のファイルツリーから[Main storyboard]を選択し、[Add Editor on Right]ボタンをクリックし、[Main storyboard]が2画面表示されるようにする
    • 【なぜ?】
      • この後の処理で[Main storyboard]に表示されている[MapView]を[ViewController.swift]に関連づけしやすくするため
        AddEditor.png
  5. [ViewController.swift]と[Main storyboard]を同時に表示する

    • 画面左側のファイルツリーから[ViewController.swift]を選択する
    • 【なぜ?】
      • [Main storyboard]と[ViewController.swift]が同時に表示されていると[MapView]の関連づけが容易にできるため
        EditViewControllerStoryboard.png
  6. MapKit用の変数(mapView)とMapViewとを関連づける

    • Ctrl+クリックで[MapView]を選択することでOutletを表示
      MapViewOutlet.png
    • [Referencing Outlets]の○印から、変数mapviewにドラッグ&ドロップで接続(connect)する
      ConnectOutlet.png
  7. 接続(connect)後の状態がこれ↓
    ConnectedOutlet.png

  8. テスト実行

    • Xcode 左上の矢印アイコンをクリック
    • Simulatorのメニューから[Debug]-[Location]-[City Run]など(移動するやつ)を選択
    • 地図を拡大表示すると、地図上を移動していることを確認できる

今回の到達点

  • Simulatorを使い、更新される位置情報に追従して地図上の位置(現在地)が移動するようになった

連載

  1. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その1)
  2. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その2)
  3. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その3)
  4. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その4)
  5. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その5)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ログイン時などに開かれる Safari をフルスクリーンにする。例:Googleログイン

はじめに

iOSエンジニアのやまたつです!
iOS13で modalPresentationStyle のデフォルトが変更されたため、 modalPresentationStyle = .fullScreen を付加していく作業を行っています。(画面数多いとつらい)

ログインに GIDSignIn を使用していたときに、「どこに .fullScreen 書くんだろう?」となったので解決策のシェアです。

解決策

SFSafariViewController でログイン画面を表示させるもの限定になりますが、
SFSarafiViewController を拡張するとOKです。

SFSafariViewController+.swift
extension  SFSafariViewController {
    override open var modalPresentationStyle: UIModalPresentationStyle {
        get { return .fullScreen}
        set { super.modalPresentationStyle = newValue }
    }
}

さいごに

SFSafariViewController は .fullScreen にせずにハーフモーダルで良いと思ってます。笑

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift未経験者が1日でアプリを作った話

こんにちは、前回投稿から相当時間が空いているのでたぶん初投稿です。

なんかアプリを作ることになってしまったんですが1日でできてしまったのでそのことを書いてみます。

~前日譚~

えらいひと「iPhoneのGPSの精度わかんないし調べようよ」
ぼく「アプリつくるんですか」
えらいひと「つくっちゃお、Mac借りてくるから」
~数日後~
社内ITのひと「Mac持ってきました」
ぼく「は~い」(マジで作るんかSwiftやったことないぞ)

構成図

swift01.png

端末からGPSの座標を取得してAzure上にデプロイしたAPIへPOSTする単純なアプリです。

いざ開発

といいつつも日ごろから触ってない環境で触ったこともない言語で一寸先は闇。困ったときはGoogle先生に頼ります。

Xcodeを入れる

Xcode入れるとiOSアプリ作れるんやね。入れたる。

プロジェクトを作る

スクリーンショット 2019-11-28 17.19.39.png

Create a new Xcode project っと。

スクリーンショット 2019-11-28 17.21.08.png

なにこれ…とりあえず機能多くないし Single View App でいいか

スクリーンショット 2019-11-28 17.23.24.png

アプリの情報適当に入れてええか。Next 押して保存場所選択するとええな。

詰む

User InterfaceSwift UI になっててGoogleに聞けども聞けども噛み合いません。
天の神に聞いたら
Storyboard 選べ」
とのこと。

なるほど、Storyboard で作るとGoogleで出てくる情報通りにいくんだなと。

頼るべきは神。

User Interface を Storyboard にして再度プロジェクト作成

ぼく「Main.storyboardがある!!!!!!!!!」

スクリーンショット 2019-11-28 17.30.47.png

Main.storyboard を選択するとデザイナーが出るのでUIのパーツを置いていきます。

スクリーンショット 2019-11-28 17.32.28.png

それっぽくなってきた。

コードを書く

天の声「Assistant Editorを開くとこのUIに紐づいたコードが表示されるらしい。」

なるほど。

~10分後~

いや。丸いアイコンのボタンないじゃん。

swift02.png

天の声「どうやらXcodeのバージョンが新しくなっていてUIが変わってるらしい。」

スクリーンショット 2019-11-28 17.40.53.png

あった。
スクリーンショット 2019-11-28 17.56.52.png

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に表示したい文言を追加っと。

スクリーンショット 2019-11-28 18.10.46.png

実機で見てみますか
image.png

:ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok: :ok:

あとバックグラウンドでの位置情報更新を許可しないといけなかった。
プロジェクトのSigning & Capabilitiesから :heavy_check_mark: を入れましょう。

スクリーンショット 2019-11-28 18.17.58.png

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の中を見てみます。

swift03.png

値が入ってる~~~~~~~~~~~!!!!!!!!!!!!!!!!!!!!!!

というわけで1日(1営業日)でアプリ(と値を受け取るAPI)ができちゃいました。
おつかれさまでした。

おわり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NISAの積み立てシュミレーションをざっくり書いてみた

概要

複利計算が電卓でやると面倒なので、ざっくり書いてみました。
細かいものは省いているのでご容赦ください。:bow_tone1:

参考

コード

//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

GitHubへのリンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スネークケースをキャメルケースに変換する方法

みなさんこんにちは!!
この記事ではSwiftにおいてスネークケース:snake:をキャメルケース:dromedary_camel:に変換する方法について解説していきます!!
(初めての本格的な技術的な記事なので稚拙な文章、知識の浅さがありますがお許しください!というかご指摘ください:bow_tone1:

今回の目的

先人の方が投稿している記事にはなぜ理解しておかなければならないのか?どういったところで活用するのかが初学者の私には難しかった:sweat_drops:
そのため理解していなかった昔の自分に説明するつもり噛み砕いて説明しようと思ったからです!(考えを整理するためでもあります!)

そもそもの話(スネークケース:snake:、キャメルケース:dromedary_camel:とは??)

キャメルケース:簡単にいうとSwiftで推奨されている形です

CamelController.swift

final class ViewController {

private func camelSample() {
    print("ラクダ")
}

このcamelSampleをみて感じることがありませんか。。。?
そうなんです。。。凸凹しているでしょ????
だんだん見えてくるんですラクダに!!:dromedary_camel:
つまりラクダみたいな塊=キャメルケースなのです:innocent:

スネークケース:JSONとかでよく返ってくる奴ら

Sample.JSON
{
first_name: "蛇塚"
last_name: "太郎"
}

APIを叩いてる時などに目にする形です。。。
先ほどのキャメルケースと違う点はどこでしょうか??
単語のつなぎ目が大文字ではなく_で繋がれています!
もう見えてきますよね!!!
_って蛇なんです:snake:
つまりは単語の区切り目を_でつないでいる=スネークケースなのです:innocent:

Swiftにおけるスネークケースの取り扱いかたについて(CodingKey)

ざっくりとキャメル、スネークケースについて理解ができたところでどんな場面で扱うことのかを解説していきます!

先ほどのデータをCodableを使用して表示することを考えていきますと。。。

SampleResponse.swift
struct User: Codable {
    let first_name: String,
    let last_name: String
}

と初学者のみなさんは直感的に書くと思います(実際に私がそうでした:baby_tone1:
しかしこれで書いてしまうと大きな問題が存在します。。。
それはコード上にキャメルケースとスネークケースが混在する危険があるということです!
Swiftではキャメルケースが推奨されているのでスネークケースがコード上に介入してくのはよろしくありません:disappointed_relieved:
(スキーに海パンでいくようなものです。。。)

気持ちが悪いですよね。。。
キャメルケースとスネークケースが混在してしましました:scream:

これを解消するのがCodingKeyです!!
SampleResponse.swift
struct User: Codable {
    let firstName: String,
    let lastName: String

    enum CodingKeys: String, CodingKey {
        case firstName = "first_name"
        case lastName = "last_name"
    }
}

これによりスネークケースをキャメルケースにすることができました!!
特定の値を取り出す時に自らがcaseで設定したもの命名から取り出すことができます!
→コード状にスネークケースが介入するのを防げました:v:

:snake:最後に:dromedary_camel:

今回は概要を理解するための簡単な内容でした。。。
なぜそのままJSONの名称を使うのがよろしくないかについては名称が縛られてしまうためです。。。
→名称は解読性に大きな影響を与えてします→x自分で考えてつけたいですよね!!
まだまだ本来は奥が深いものですが先人様達の記事を参考にして勉強していきます!

今回は概要だけ、さらには稚拙な文章でしたが役に立つことができれば幸いです:relaxed:

次回の記事は本格的なAlamofierの使い方について解説していきます!!(今回の具体的なコードも掲載する予定です)

質問点やご指摘があればどしどし絡んできてください:bow_tone1:(虐めないでくださいね。。。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.swift
protocol 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と同じ。ビジネスロジックを担うレイヤー。
  • ViewViewControllerも含めたUIViewのサブクラス。UIイベントを受け付け、Presenterに伝える。
  • Presenter:ViewとModelの仲介役。Viewからの入力を受け、Modelにコマンドを送る。Modelからの状態更新を受け取り、Viewを更新。Viewに表示するためのデータを保持するのもここ。

ViewとModelを完全に分離したいという目的はMVCと共通ですが、MVPではさらに描画処理とプレゼンテーションロジックを分離するという目的があります。

ここで言う「描画処理」というのは、label.text = newTexttableView.reloadData()という文字通り描画のための処理のことで、「プレゼンテーションロジック」というのは、ビューの更新やページ遷移(どの内容を表示するのかを決定する処理)などのUIのビジネスロジックを指します。

MVCではViewControllerにまとめらていたこれら2つの処理をViewとPresenterにそれぞれ分離することで、FatViewControllerを避けられるということです。

以下に示すのはサンプルのPresenterの実装です。

RepositoryViewPresenter.swift
protocol 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.swift
class 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という一方向のデータフローが生成されます。このことのメリットは、アプリケーションが複雑化したり、それに伴って開発者が増えたとしてもデータフローが追いやすく、保守性の高いコード書けることだそうです。

今回作ったアプリは機能的にすごくシンプルなものだったため、このメリットはほとんど享受できていません。そればかりか、慣れない複雑な設計を用いたために、実装しづらかったというのが正直な感想です。

エンジニアであれば、「よりスケールしやすい新しくて重厚なアーキテクチャパターンを使いたい」という気持ちは多かれ少なかれあるでしょうが、アーキテクチャパターンの選定においては、開発するアプリの性質やメンバーの習熟度なども考慮しなければならないと考えると難しいところですね。

実装に関しては、こちらのコードを参考にさせていただきました。

感想

一言で感想を述べると、「やっぱり設計は難しい!!」ということにつきますね。

今回取り上げたものの他に、ReduxClean Architecture, VIPERなども今では主要なアーキテクチャパターンとして知られています。

アーキテクチャパターンを勉強しようとする際の大きな動機の1つとして、「開発するソフトウェアの性質に応じた適切な設計ができるようになりたい」というものがあるでしょう。

私も例外ではなくそう思っていたのですが、主なものだけでもこれだけあるアーキテクチャパターンを十分に理解し、実装できるだけの技術力を身につけ、その上で適切な設計を行えるようにするというのは実際かなり険しい道のりになりそうです…

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Objective-C Bridging Headerファイルを編集したら大量のビルドエラーが出る時の対処

なにが起きたか

Objective-C Bridging Headerファイル(Objective-C-Bridging-Header.hとか)を編集した後にビルドしたら大量のビルドエラーが発生した:innocent:
Objective-C_Bridge_Header_Change.png

対処

  • エラーが出てるファイルに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がなくても、ライブラリが機能するようにということらしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む