20191028のiOSに関する記事は7件です。

Flutterウィークリー #81

Flutterウィークリーとは?

FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/

この記事は#81の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-81

※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。

読み物&チュートリアル

複数の画面とそれらの操作方法

https://blog.codemagic.io/flutter-tutorial-multiple-screens-and-how-to-navigate-them/


Flutterアプリの画面内をナビゲートする方法に関するScott Stollのチュートリアル。

Googleマップを使用したFlutterクラスターマーカー

https://coletiv.com/blog/how-to-cluster-markers-on-google-maps-using-flutter/


AntónioValenteは、マップ内の複数のポイントをクラスター化するのに役立つパッケージであるFlusterの使用方法を示します。

Ultimate JavaScriptとDart構文ガイド

https://flutterigniter.com/ultimate-javascript-dart-syntax-guide/


あなたはJSのバックグラウンドであり、 Dartですか?これは、両方の言語を比較する非常に便利なガイドです。

Flutter Mobile:スクラムポーカーアプリ

https://myracle.hashnode.dev/flutter-mobile-scrum-poker-app-ck1vx6yxl004f3os10qi8u0o2


スクラムポーカーアプリケーションFlutterをゼロから作成する方法に関するMax Weberの素晴らしいチュートリアル。

60 Days of Flutter :メッセンジャーの構築

https://medium.com/@adityadroid/60-days-of-flutter-building-a-messenger-day-60-wrapping-it-up-ae645037610b


Aditya Gurjarは、 Flutter完全なアプリを作成した経験を説明する、この驚くべき26の記事のセットを完成させました。

Flutterスワイプして閉じるを実装するための詳細なダイビング

https://medium.com/flutter-community/an-in-depth-dive-into-implementing-swipe-to-dismiss-in-flutter-41b9007f1e0


Wilberforce Uwadiegwuによる詳細な記事で、リストにスワイプ効果を実装する方法を学びます。

A-My Languageを話せますか?!

https://medium.com/@greg.perry/do-you-speak-a-my-language-587854c2d0a3


グレッグペリーは、さまざまな言語をサポートするアプリを作成するためのすべての手順を詳しく説明します。

Flutter密度グラフを描画する

https://medium.com/flutter-community/drawing-density-charts-in-flutter-7f1c6a14ee1c


Dan Panaiteは、複数の点が散らばったグラフを表示するさまざまな方法を示しています。

国際化? Flutter簡単な翻訳と国際化

https://medium.com/flutter-community/i18n-extension-flutter-b966f4c65df9


Marcelo Glasbergによるこのチュートリアルで、多言語アプリの作成にi18nパッケージを使用する方法を学びます

Flutterパーティクルシステム(パフおよびザップ用)

https://medium.com/flutter-community/particle-systems-for-puffs-and-zaps-with-flutter-d32cdcc97ef3


Ivan Cherepanovによるこのチュートリアルを使用して、UIにクールな輝く効果を追加します

ITテストでの国際化文字列の再利用

https://medium.com/flutter-community/reusing-i18n-strings-for-it-testing-in-flutter-cead2aa5c914


Efthymis Sarmpanisは、テストでローカライズされたリソースが必要な場合の問題の解決策を提案しています。

Dart便利なリストメソッド

https://medium.com/flutter-community/useful-list-methods-in-dart-6e173cac803d


Dart最高のリスト演算子の説明。

ビデオ&メディア

BLoCを使用したFlutterクリーンアーキテクチャの実装

https://skillsmatter.com/skillscasts/14431-flutter-london-october#video


Amr Yousefによるこの講演で、クリーンアーキテクチャアプローチを適用したBLoCの使用方法を学ぶ

Flutter付きの美しいアニメーションアプリバー!

https://www.youtube.com/watch?v=SkkmoT_DZUA&feature=youtu.be


移動するAppBarの作成方法に関するチュートリアル。

DashCastが帰ってきた! (退屈なFlutter開発ショー、Ep。32)

https://www.youtube.com/watch?v=d2gRn6cOEhA&list=PLjxrf2q8roU3ahJVrSgAnPjzkpGmL9Czl&linkId=75812192


RSSの解析やオーディオのストリーミングなど、ポッドキャストアプリの構築に必要なものの例。

DataTable(今週のFlutterウィジェット)

https://www.youtube.com/watch?v=ktTajqbhIcY&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=58&t=0s


テーブルでユーザーに表示する重要なデータはありますか?列、行を定義し、DataTableでセルのサイズを自動的に設定します!

Flutter AutoSizeText(今週のパッケージ)

https://www.youtube.com/watch?v=OHhBa_1mWlQ&feature=youtu.be


AutoSizeTextパッケージを使用して、制約内のテキストのサイズを自動的に変更します。

Flutter Blocライブラリチュートリアル(1.0.0以降)–反応状態管理クラッシュコース

https://www.youtube.com/watch?v=hTExlt1nJZI


最近リリースされたBLoCライブラリ1.0.0のチュートリアル。

ライブラリ&コード

jama5262 / jiffy

https://github.com/jama5262/jiffy

Jiffyは、日付の解析、操作、および書式設定のためにmomentjsに触発されたFlutterおよびdart Web日付時刻パッケージです

Norbert515 / pimp_my_button

https://github.com/Norbert515/pimp_my_button

パーティクルエフェクトをあらゆるものに追加します。

BLoC 1.0.0

https://bloclibrary.dev/#/

バージョン1.0.0を達成したBLoCライブラリの公式Webサイト

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

「iPhone 設定をアプリから操作してらくらく環境構築」のQRコードの内容の解析編

この記事は iOSDC 2019 LT の「iPhone 設定をアプリから操作してらくらく環境構築」の補足です。

背景

iOS 11 から Wi-Fi を QR コードで設定できるようになって便利ですよね。ただ、事情があって iOS 11 へあげられない方もいると思います。その方向けに iOS 11 未満でも使える Wi-Fi 設定ライブラリ WiFiQRCodeKit を作成してみました。その過程はちょっと面倒でした。

ちょっと面倒の詳細

QR コードの読み取り

まず、標準ライブラリで QR コードを読み取れるようになるのは iOS 11 からです。

iOS では QR コードの読み取りライブラリが豊富です。そのため、ここでは苦労しませんでした。今回は yannickl/QRCodeReader.swift を使うことにしました。

QR コードの形式調査

まず、QR コードにどのような形式で Wi-Fi の接続情報がエンコードされているのか調べることからはじめました。最終的に zxing/zxing の Wiki にエンコード形式が書いてあるとわかりました。例えば、SSID が「mynetwork」、暗号化方式が WPA (or WPA2)、パスワードが「mypass」の Wi-Fi に接続する QR コードの内容は以下のような文字列です:

WIFI:T:WPA;S:mynetwork;P:mypass;;

前述の Wiki にも書いてある通り、これは太古に docomo が策定していた MeCard の形式を真似ています。この形式の仕様は Internet Archive からしか見つからないのは趣深いですね:relaxed:

さて、これから作成するライブラリではこの内容から接続情報を取り出さなければなりません。このときに必要になる技術が構文解析です。この構文解析の入力と出力のイメージは次のようなものです(あくまでイメージです):

let reuslt = parseWiFiQRCode(text: "WIFI:T:WPA;S:mynetwork;P:mypass;;")

XCTAssertEqual(result.ssid, "mynetwork")
XCTAssertEqual(result.password, "mypass")
XCTAssertEqual(result.encrytionType, "WPA")

これだけを見ると、次のような愚直な実装が考えられるかもしれません:

StupidParser.swift
// 愚直な構文解析の実装(後述する一部のケースで壊れます)
func parseWiFiQRCode(text: String) {
    let content = text.replacingOccurences(of: "WIFI:", with: "")
    let fields = content
        .split(separator: ";", omittingEmptySubsequences: true)
        .map { field in field.split(":") }

    let ssid = fields.first { field in field[0] == "S" }
    let password = fields.first { field in field[0] == "P" }
    let encryptionType = fields.first { field in field[0] == "T" }

    return (ssid: ssid, password: password, encryptionType: encryptionType)
}

しかし、注意しなければならない点があります。SSID やパスワード は「:」や「;」を含む可能性があるのです1。この場合は「\:」や「\;」のようにエスケープをすることになっているようです2。このように、エスケープの規則が含まれるような場合に split を使ってしまうと、「\;」のような区切り位置ではない場所でも区切ってしまうため、不具合になってしまいます。このぐらいの例であれば、正規表現で頑張って抜き出すこともできるかもしれません。しかし、SSID やパスワードの出現順序は不定で、かつ暗号化方式は省略可能という仕様なため、その正規表現はかなり複雑なものになるはずです。頑張って書いてみるとこんな感じでしょうか:

/WIFI:(?:(T:(?:[^\\]|\\[:;\\])*)|(S:(?:[^\\]|\\[:;\\])*)|(P:(?:[^\\]|\\[:;\\])*);)+;/

ただし、この結果で抜き出された文字列にはまだエスケープが残っています。そのため、エスケープを外す処理をこの先に書かなければなりませんが、そこそこ面倒な処理になります3。そうでなくとも、Swift における正規表現処理は面倒すぎて使いたくありません。

そこで、今回は Monadic Parser Combinator を使うことにしました。これは構文解析の処理のデザインパターンのひとつです。このデザインパターンによる実装は高速ではないものの、再利用が容易で静的型検査によく馴染む堅牢な書き方をできます。ただ、これを解説するとすごく長くなってしまうので割愛します。要するに、構文解析に一工夫がいることを説明したかったのです。

なんとかこれで Wi-Fi の接続情報を取得できるようになりました。

終わりに

たかが QR コードの内容の解析1つとっても、いろいろな知識が必要なのは辛いですね :cry:


  1. 例えば SSID は 32 bytes の octet string なので、これらの記号や絵文字なども含められます。 

  2. Wi-Fi の接続情報を QR コードにしてくれるいくつかのサービスは、このエスケープの実装をさぼっているようです。もし、QR コードでは自分の Wi-Fi に接続できない、という方はこれが原因かもしれません。 

  3. /\\([^:;\\))/$1/ でいいじゃん!と思ったあなたは "a\\b" を試してみるといいでしょう。 

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

【実践編: 内側の2つの円】iOSでVIPERとClean Architecture 円の内側から作る

前回の記事の続きのVIPER Clean Architectureの実践編である。

この記事では内側の2つの円の部分まで作成する。今回カバーする部分はとても簡単なのでほとんどの人が問題なく読み進められると思う。

今回作るのは以下の図解の 真ん中の2つの円、EntitiesとUse Cases の部分だ。これを円の内側から順番に作っていく。

CleanArchitecture.jpg

The Clean Architecture by Robert C. Martin (Uncle Bob)

なぜ内側から作るのか?

内側から実装するにのには理由がある。

1. 実装しやすい

円の内側は依存のもっとも深部であり、その先に依存する部分がない。依存する先がないとうことは、依存部分のStubを作ったり、その部分がどうなるかを一切想定しなくても作れるということだ。

2. 作る物の本質がわかる

円の内側は本質的に表現したいことであり、抽象的である。UIの表現に惑わされず本質的に何を作ろうとしているかがわかり、理解を促進する。

3. 実践に近い

実際に開発をする場合、最終的に表現されるUIの部分は、最後まで固まらないことが多い。しかし本質的に何を表現するかは、比較的初期に決まり大きく変化しないことが多い。

例えばユーザーの一覧を表示する画面を作成する時、User Entityを作成し、それをAPI経由でlistとして取ってきて、List状に表示するということは初期に確定するだろう。

しかし実際のUIで具体的にどう表現をするかは最後までわからなかったり途中で変わったりする可能性が高い。

例えばUserをListとして表示する場合、

  • TableView
  • CollectionView
  • PageView(各ページに1ユーザーを表示)

のような複数の実装方法が想定可能である。

もしMVC設計して実際のCell等に直接Userを渡して情報を表示させるような設計をした場合、Clean Architectureで分割されている全てのコンポーネントのコードがViewControllerを中心に密接に繋がってしまい、UI変更のコードへのインパクトはClean Architectureで設計した場合と比べ大分大きくなってしまう可能性がある。

作るシステムの概要

Userの一覧を取ってきて表示するという単純なシステムを作成する。

Simulator Screen Shot - iPhone 8 - 2019-10-28 at 17.39.59.png

FrameworkとフォルダのGroup分け

VIPERやClean Architectureの恩恵を最大限に受けるためには、それぞれのコンポーネントをしっかりFrameworkとして分けることが重要だ。

これにより、依存関係の理解が促進されたり、間違った実装をしてしまうリスクを大きく減らせる。

さらに、1つの画面の開発をUI部分やロジックの部分と複数人で分業しての開発もやりやすくなる。

Group構造とClean Architectureの円を一致させる

Xcode上でRoot配下のGroupは全てClean Architectureの一つの円を表すようにする。

各Frameworkとその配下のディレクトリ構造は、Xcode上と基本的には一致させる。
しかし一部Clean Architectureの円の構造を表現するためにXcode上でGroupはあるが実態としてのフォルダは作成しない部分がある。

具体的にはFrameworkは全てRoot直下に実際のディレクトリを作成する。各Framework配下のディレクトリ構造も基本的にはXcodeのGroupと一致させる。

ただしClean Architectureの外側2つの円は、円の中に複数のFrameworkがあるため、この部分はXcode上ではGroupを作成するが実際のディレクトリは作成しない。さらにこの配下にあるGroupは実際のディレクトリを持つ。

最後にClean Architectureの図解の外側にもう一つ円を追加する。

最終完成形

この記事ではカバーしていない部分も含め全てが完成した場合、このような構造になる。

  • Outer Circle(without folder)
    • App Name(framework)
    • Application(without folder)
    • Routers
    • Settings(framework)
  • View(framework)
  • Interface Adopter(without folder)
    • Presenter(framework)
    • Gateway(framework)
  • Use Case(framework)
    • Request Base
    • Interactors
  • Entity(framework)
    • Entities

今回の記事で作る部分

実際今回の記事でカバーするのは以下の部分のみだ。

  • Use Case(framework)
    • Interactors
  • Entity(framework)
    • Entities

実装準備

もっとも内側のEntityの円の部分を作成し始める前に、事前準備として、Xcodeのプロジェクト作成直後にできたメインのTargetの部分を一番円の外側に作成するもう一つの円の部分を表現するようなXcodeのグループ構成にする。

今回は UserManager という名称のXcodeプロジェクトを作成する。
作成直後、Groupを以下のような構造にする。

  • Outer Circle(without folder: 新たに追加)
    • UserManager(最初から存在するものを移動)
      • Application(without folder: AppDelegateやInfo.plistなど自動生成ファイルを入れておく)

最初の円: Entity

Frameworkとファイルの配置

最初の円であるEntityのFrameworkを作成し、その配下にEntitiesディレクトリを作成。
そこにUser.swiftのファイルを配置する。

  • Outer Circle(without folder: 新たに追加)
    • UserManager(最初から存在するものを移動)
  • Entity
    • Entities
    • User.swift

User Entity

User Entityは以下のようになる。

User.swift
public struct User {

    public let id: Int
    public let name: String

    public init(name: String, id: Int) {
        self.id   = id
        self.name = name
    }
}

何の変哲もない実装だが、重要なのはUser Entityそのものが他のどのFrameworkにも依存していないとうことだ。

実際にUser structが他のどのFrameworkへもアクセスしていないと同時に、EntityのFrameworkのTargetのFrameworks and Librariesの部分で何もImportしないことでそこを確約する。

2つ目の円: Use Cases

Frameworkとファイルの配置

UseCase Frameworkを追加し後述の図解の通りのGroup構造を作成する。

  • Outer Circle(without folder)
    • UserManager
    • Application(without folder)
  • UseCase
    • Interactors(New!)
    • UserListInteractor.swift(New!)
  • Entity
    • Entities
    • User.swift

さらにUseCaseからはEntityへの参照が必須なのでUseCaseのTargetの部分のFrameworks and LibrariesでEntityをインポートする。

Usecase Interactor

ここで追加したUserListInteractorが今回この層で実装するInteractorだ。

UserListInteractor.swift
import Entity

public class UserListInteractor {

    var users: [User] = []
}

UserListInteractorはEntityをimportしてUser Entityを直接参照しており、UserListInteractorはEntityに依存していることがわかる。

さてUserListInteractorはどうやってUserの一覧を取ってくれば良いのだろうか?ここでGatewayが登場する。Gatewayに関しては次回の記事で扱う。

まとめ

今回は非常に単純な例だったが、Entityが分離されているだけでも実際大きな恩恵を受けられる。小さなプロジェクトの場合はEntityだけを分離してそのほかの部分は一つ外側の円に全て入れるようなミニマムなFramework分けでもいいかもしれない。

次回はいよいよVIPER, Clean Architectureの重要かつ比較的難しい部分のより外側の実装に移っていく。

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

【iOS13】scene(_:willConnectTo:options:)のオプションとセッションの NSUserActivity の違いについて

【iOS13】scene(_:willConnectTo:options:)のオプションとセッションの NSUserActivity の違いについて

はじめに

scene(_:willConnectTo:options:)でのNSUserActivityからのシーンの復元処理について、多くのAppleのテンプレートでは以下のようになっています。

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let userActivity = connectionOptions.userActivities.first ?? session.stateRestorationActivity {
            // UIの復元処理を行う
        }
    }

この時に、sessionまたはconnectionOptionsから得られるNSUserActivityの違いがよくわからなったので調べたことを、まとめておきたいと思います。

長くなったので、先にまとめを記載しておきます。

まとめ

  • scene(_:willConnectTo:options:)connectionOptionsはシステム側でのシーン作成時に、デリゲートへのパラメータ受け渡しに使われる。
  • ただしconnectionOptions.userActivitiesには、Handoffに関するユーザーアクティビティは入ってこない。handoffUserActivityTypeにタイプ情報だけが提供される。
  • Handoffのユーザーアクティビティはscene(_:willContinueUserActivityWithType:)という、専用のデリゲートメソッドに入ってくる。
  • Appleのテンプレート通りの実装なら、connectionOptions.userActivities.firstsession.stateRestorationActivityにも同じユーザーアクティビティが入ってきそう。

WWDC2019-212

まず、WWDC2019-212の中盤ConnectionOptions#userActivitiesについて言及されている部分を抜き出します。

Now, in this template that I pulled out, we're already looking for any user activities that we have gotten through Handoff or any other system facilities that vend us user activities. We want to prefer those user activities because that's what the user actually did.
WWDC2019-212 Transcript

But the stateRestorationActivity is special and it's on the session. So if you don't have one of those activities, we want to use our stateRestorationActivity.
WWDC2019-212

とあり、「Handoffまたは他のシステム機能を介して取得したユーザーアクティビティ」がconnectionOptions.userActivitiesに入っているように言われています。次点で、セッションが保持しているstateRestorationActivityからユーザーアクティビティを取得するようです。もっと詳しいところをドキュメントで調べました。

APIドキュメントではどうか

まずUIScene.ConnectionOptionsから見てみます。

A data object containing information about the reasons why UIKit created the scene.
UIKit creates scenes for many reasons. It might do so in response to a Handoff request or a request to open a URL. When there is a specific reason for creating a scene, UIKit fills a UIScene.ConnectionOptions object with the associated data and passes it to your delegate at connection time.

「Handoff」や「URLを開くリクエスト」など、システム側でシーンが作成される時、UIScene.ConnectionOptionsにパラメータを詰めてデリゲートに渡されるようです。
しかしながら、このオプションのuserActivitiesプロパティの説明には、

This property does not contain user activity objects related to Handoff.

とあり、Handoffに関するユーザーアクティビティは含まれないとあります。続いて、

UIKit delivers only the type of a Handoff interaction in the handoffUserActivityType property. Later, it calls additional methods of the delegate to deliver the NSUserActivity object itself.

とあり、シーンが起動された際にNSUserActivityactivityTypeとしてUIScene.ConnectionOptions#handoffUserActivityType のタイプ情報のみが提供されるようです。
その後、別のデリゲートメソッドにHandoffのユーザーアクティビティは渡されるとのこと。「additional methods of the delegate」が示すところを探してみると、

Tells the delegate that it is about to receive Handoff-related data.

シーンベースでHandoffのユーザーアクティビティを受け渡しする専用デリゲートメソッドが用意されてました。WWDCの動画での言及内容のみですと、この辺はやや誤解してしまう印象があります。

ちなみに、アプリケーション単位でHandoffでのユーザーアクティビティの受け渡しを行うのは application(_:continue:restorationHandler:) というAPIがあります。

で、結局connectionOptions.userActivitiesプロパティには、何のユーザーアクティビティが入っているかというと、

You create user activity objects at key moments in your scene's lifetime and register them with the system.

と続き、自分でシステムに対し明示的に登録したユーザーアクティビティのようです。この登録に使用するAPIは、どれのことでしょうか。これはUISceneDelegate#stateRestorationActivity(for:)に記載がありました。

When reconnecting the scene later, UIKit includes the NSUserActivity object in the UIScene.ConnectionOptions passed to your scene delegate's scene(_:willConnectTo:options:) method.

とあり、このstateRestorationActivity(for:)を介して渡したユーザーアクティビティが、デリゲートのscene(_:willConnectTo:options:)でのconnectionOptions引数に入るようです。
てっきりstateRestorationActivity(for:)で保存したユーザーアクティビティが、APIの命名的に対になっていそうなsession.stateRestorationActivityに入ると思いましたが、ではsession.stateRestorationActivityには何が入るか?

Before disconnecting a scene, the system asks your delegate for an NSUserActivity object containing state information for that scene. If you provide that object, the system puts a copy of it in this property.

とドキュメントにあり、アプリ切断時にデリゲートを通してシステムに提供したNSUserActivityが、再接続時にこのプロパティにコピーされるとのこと。

UISceneDelegate で NSUserActivity をシステムに提供できるのは、

stateRestorationActivity(for:)
Returns a user activity object encapsulating the current state of the specified scene.

と記載のあるstateRestorationActivity(for:)のことだと思われます。Appleのテンプレート通りの実装では、

    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        return scene.userActivity
    }

とします。つまりこの場合、connectionOptions.userActivities.firstsession.stateRestorationActivityのどちらにも同じユーザーアクティビティが入ってきそうです。

確かに、次の項目で紹介するAppleのサンプルコードで検証した感じではそのようでした。

スクリーンショット 2019-10-28 14.55.54.png

シーンのユーザーアクティビティを保存する方法

付随して、サンプルコードで見つけたユーザーアクティビティの保存に関するパターンも追記しておきます。

・パターン1

Sample Code - Supporting Multiple Windows on iPad

特定のViewController内で
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        view.window?.windowScene?.userActivity = photo?.openDetailUserActivity
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        view.window?.windowScene?.userActivity = nil
    }

・パターン2

SampleCode - Restoring Your App’s State

UISceneDelegateで
    // シーンが非アクティブになる時に呼ばれます
    func sceneWillResignActive(_ scene: UIScene) {
        if let navController = window!.rootViewController as? UINavigationController {
            if let detailViewController = navController.viewControllers.last as? DetailViewController {
                // Fetch the user activity from our detail view controller so restore for later.
                scene.userActivity = detailViewController.detailUserActivity
            }
        }
    }

いずれもsceneオブジェクトのuserActivityプロパティに任意のタイミングでユーザーアクティビティを入れておきます。入れておいたユーザーアクティビティは、この後以下のデリゲートを介してシステム側に渡されるという寸法です。

    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? {
        return scene.userActivity
    }

以上、まとめ に戻る

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

FlutterでHello worldを動かすまでの環境構築手順(iOS, Android)

FlutterでHello worldを動かす

仕事でFlutterを扱うことになったので、とりあえずHello world動かすところまでの流れをメモ。

「Flutterとは何ぞや?」って人は公式サイトを見てくれ。
Flutter - Beautiful native apps in record time

環境

Flutter SDKは、Windows、macOS、Linux向けに提供されていますが、本記事ではmacOS上での環境構築手順をまとめます。

OS バージョン
macOS Mojave バージョン10.14.6

Flutter SDKは「v1.9.1+hotfix.4」を使いました。
※バージョンアップ等で本記事の情報が古くなっている場合は、公式ドキュメントを参照してください

Flutter SDKのインストール

まずはFlutter SDKをインストールします。
macOS install - Flutter

Flutter SDKのダウンロード

公式サイトから最新版SDKのzipファイルをダウンロードします。
本記事執筆時点のSDKバージョンは、v1.9.1+hotfix.6となっていました。
スクリーンショット 2019-10-28 14.17.15.png

インストール対象フォルダに移動し、ダウンロードしたzipファイルを解凍します。

$ cd ~/development
$ unzip ~/Downloads/flutter_macos_v1.9.1+hotfix.6-stable.zip

パスを通す

SDKを展開しただけではflutterコマンドが使えないので、パスを通します。

$ export PATH="$PATH:`pwd`/flutter/bin"

.bash_profileに直接追記してもOKです。

export PATH="/Users/unsoluble_sugar/flutter/bin:$PATH"

開発用バイナリをダウンロード

必要に応じてプラットフォーム固有の開発バイナリをダウンロードします。

$ flutter precache

flutter doctorを実行する

セットアップ完了のために別途インストールが必要なツールがないか、依存関係をチェックします。

$ flutter doctor

flutter doctorコマンドを実行すると以下のようなログが表示されます。flutterコマンドが使用できない場合は、前述のパスが通っていないケースが多いです。

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.9.1+hotfix.4, on Mac OS X 10.14.6 18G103, locale ja-JP)

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[!] Xcode - develop for iOS and macOS (Xcode 11.1)
    ✗ CocoaPods installed but not initialized.
        CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that
        responds to your plugin usage on the Dart side.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/platform-plugins
      To initialize CocoaPods, run:
        pod setup
      once to finalize CocoaPods' installation.
[✓] Android Studio (version 3.5)
[!] Connected device
    ! No devices available

! Doctor found issues in 2 categories.

[!] の付いている項目は準備が完了していない項目です。

Android、Xcode関連はのちほど設定するので無視してください。Connected device もデバッグ時に使用するものなので、いまは気にせず大丈夫です。

[✓] Flutter となっていない場合は、Flutterの設定まわりに何かしら問題が起きている状態です。表示されているメッセージを参照しながら対応しましょう。

基本的には指示通りに不足ツールをインストールしていけば解決します。

バージョン確認

flutter doctorでもflutterのバージョン確認はできますが、flutter --versionコマンドを打つと、使用されているDartのバージョンなども確認できます。

$ flutter --version
Flutter 1.9.1+hotfix.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision cc949a8e8b (4 weeks ago) • 2019-09-27 15:04:59 -0700
Engine • revision b863200c37
Tools • Dart 2.5.0

これでflutter SDKの設定は完了です。

iOSのセットアップ

続いてiOSのセットアップを行なっていきます。

Xcodeをインストールする

Xcodeの最新安定版をインストールします。公式Webサイト、もしくはMac App Storeからダウンロードします。
スクリーンショット 2019-10-28 14.56.01.png

コマンドラインツールの設定

Xcode command-line toolsの設定をします。

$ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
$ sudo xcodebuild -runFirstLaunch

一度もXcodeを起動していない場合の初期セットアップや、ライセンスの同意など、必要に応じて進めましょう。

iOSシミュレーターの実行

以下コマンドでiOSシミュレーターを起動します。

$ open -a Simulator

スクリーンショット 2019-10-28 15.08.42.png

Xcode上から [Xcode] > [Open Developer Tool] > [Simulator] で起動することもできます。
スクリーンショット 2019-10-28 15.07.20.png

サンプルアプリの作成

flutter createコマンドで新規プロジェクトを作成します。

$ flutter create my_app

シミュレーターを起動した状態でflutter doctorコマンドを打つと、Connected device項目が[✓]となっていることがわかります。

[✓] Connected device (1 available)

作成したプロジェクトフォルダに移動し、flutter runコマンドを実行しましょう。

$ cd my_app
$ flutter run

起動中のシミュレーター上でアプリが実行されます。

スクリーンショット 2019-10-28 15.11.24.png

右下の「+」ボタンをタップすると、画面中央の数字がカウントアップされるHello worldアプリらしい。

起動確認ができたらiOSのセットアップは完了です。

Androidのセットアップ

iOSの動作確認ができたので、Androidのセットアップも行なっていきます。

Android Studioのインストール

Android Studioをダウンロードしてインストールします。
スクリーンショット 2019-10-28 15.22.19.png

Android SDK等のインストール

Android Studio Setup Wizardに従って、最新のAndroid SDK、Android SDK Platform-Tools、Android SDK Build-Toolsをインストールします。

Android Emulatorを作成

Android Studioを起動し、Welcome画面右下にある [Configure] メニューから [AVD Manager]を開きます。
スクリーンショット 2019-10-28 15.24.05.png

適当にEmulatorを作成し、対象EmulatorのPlayボタンを押します。
スクリーンショット 2019-10-28 15.26.16.png

Emulatorが起動します。
スクリーンショット 2019-10-28 15.28.04.png

Emulatorが起動した状態でflutter runコマンドを実行すると、サンプルアプリが起動します。
スクリーンショット 2019-10-28 15.34.01.png

簡単ですね。

iOS、Androidで同時に動作確認する

おまけ。

iOS Simulator、およびAndroid Emulatorを同時に起動した状態で以下コマンドを打ってみましょう。

$ flutter run -d all

複数OSでアプリ挙動を同時確認することが可能です。
スクリーンショット 2019-10-28 15.32.34.png

すごい。

エディタはVSCodeがおすすめ

ちなみにエディタはVSCodeを使っています。
flutter_hot_reload.gif

とりあえずFlutterとDartのプラグイン入れとけば、必要最低限の環境は整います。

VSCode上から新規プロジェクトの作成、エミュレーターの起動、デバッグまでひと通りこなせます。
スクリーンショット 2019-10-28 15.43.06.png
FlutterにはHot reload機能もあるため、変更の確認が容易で高速開発が可能。
Hot reload - Flutter

すごい。

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

NativeScriptを用いたVue.jsモバイルアプリ開発の環境構築

概要

NativeScriptを用いてVue.jsモバイルアプリを作る際の環境構築の手順をまとめました.

  • NativeScript・・・本物のモバイルネイティブアプリをJavaScriptで開発するためのオープンソースフレームワーク
  • NativeScript-Vue・・・Vue.jsを使ってモバイルアプリを作成するためのNativeScriptのプラグイン

環境

  • Mac OS X 10.14.6
  • Node.js v12.12.0
  • NativeScript 6.1.2
  • NativeScript-Vue 2.4.0

手順

NativeScriptのシステム要件のセットアップする

下記ページに従い,macOSでNativeScriptアプリをビルドして実行するために必要なすべてのシステム要件をセットアップします.
最後の手順でtns doctorコマンドを実行して問題が検出されなければ次のページに進む前に本記事に戻り,次の手順に進んでください.

NativeScript Advanced Setup — macOS - Native
Script Docs

「結局英語で書かれた公式ドキュメントかよ,読むの面倒だからQiita記事探してるんだよ」と思った方もいるかもしれません.しかし私の経験上,今回のような導入手順や環境構築に関しては最初から公式サイトを読みにいくのが最も時間を無駄にせずに済みます.

また,Androidエミュレータ(AVD)のセットアップ手順において,いくつか選択肢が示されていますが,もしAndroid Studioを使う場合は,以下の手順で日本語化できます.

MacでAndroid Studioを日本語化する - ソフラボの技術ブログ

NativeScript-Vueをインストールし開発を開始する

下記ページに従い,NativeScript-Vueのインストール,プロジェクト作成,ビルドを行います.幸いこちらは日本語ですね.

クイックスタート - NativeScript-Vue

tns run android --bundleコマンドでAVDが立ち上がれば成功です.
ちなみにこれはPixel 2のAVDを使用した場合です.

スクリーンショット 2019-10-22 15.41.21.png

動作確認

app/components/App.vueにmsg: "Hello!"と書かれた箇所があるので,それを編集してみてください.

スクリーンショット 2019-10-28 10.20.30.png

画面に表示されているメッセージが自動で変わると思います.以上で環境構築は終わりです.

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

[iOS13] 既存アプリのiOS13対応でハマったこと(現在進行形)

既存アプリのiOS13対応。楽勝かと思ったらアンドキュメントな罠が。。。

現在進行形な既存アプリのiOS13対応で、アンドキュメントな変更によりはまったところを、同じ状況な人のために残しておきます。

CoreBluetooth Peripheralの取れるタイミング

今までは、

objective-c
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

の後に、

objective-c
[_centralManager retrievePeripheralsWithIdentifiers:identifiers];

で取れたんですが、(いや、実際には、取れるとは限らないんですが)
iOS13になってからは、ちゃんと

- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

のイベントが通ってからでないと、ペリフェラル一覧が取得できなくなりました。
いや、そもそも前からそう書くのが正解のはずなんですが、僕のところに回されてきたプロジェクトの元のコードが、、、げふん

参考 https://www.jenx.si/2019/10/02/changed-bluetooth-initialization-on-ios13/

Settings.bundle 同名グループはダメ

これも暗黙のルールだったんでしょうが、、、
[iOS13] Settings.bundleのグループ名が重複すると設定アプリの内容が正しく設定されない

画面横向き(Landscape)の時はステータスバーを表示できない。

当然、prefersStatusBarHidden をセットしたり、info.plistで設定したり、古い書き方のAppDelegateで設定したり、回転時に表示させようとしたり、色々やってるんですが、横向き時にステータスバーを表示させることができません。Appleのdeveloper forumなんかでも同様の報告がありますが、現状最新ベータ版でも直ってません。うーむ、、、

モーダル表示画面の親。viewWillApearが呼ばれない。

モーダルビューの表示方法のデフォルトが変更された件で、みなさん「フルスクリーンに設定して表示の変更を回避」ってやってると思うので気がついてないと思いますが、裏に画面が見えてるということは、モーダル画面を閉じた時に親の「ViewWillApear」が呼ばれないのです。フルスクリーンにしない人は気を付けましょう。

以上、後半は昼休み終わりかけてダレたので、スクショもコードもありませんが!

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