- 投稿日:2020-02-16T23:57:54+09:00
Human Interface GuidelineにおけるModalのUIBarButtonItemの規則
iOS13になってから標準でmodalがiOSアプリ内でも使えるようになり、色々なアプリでmodalをみるようになったと思います。今回は凄いニッチな話ですが、UINavigationControllerで表示したmodalのUIBarButtonItemの命名規則についてまとめていきます。
想定する読者
- アプリ開発をしているUIデザイナー
- iOSエンジニア
- Human Interface Guidelinesを勉強したい人
この記事のゴール
この記事を読んだ人・書いた僕自身が今後理由なくしてmodalのUIBarButtonItemの実装でHIGに従ってないアプリを作成しないこと
そもそもmodalとは
Human Interface Guidelinesによると
Modality is a design technique that presents content in a temporary mode that’s separate from the user's previous current context and requires an explicit action to exit. Presenting content modally can:
Help people focus on a self-contained task or set of closely related options
Ensure that people receive and, if necessary, act on critical information日本語訳すると
modalとはユーザーにとって、前のコンテキストとは別の一時的な状態でコンテンツを提示するデザイン手法であり、終了するには明示的なアクションが必要なものである。モーダルは
- 自己完結型のタスクまたは強い関連のある一連のタスクにユーザーが集中できるようにする
- 人々が重要な情報を受け取り、必要に応じて行動する
ものである。
つまり、簡潔に言うとモーダルは
- タスクを行うためのもの
- タスクを完了するか、放棄することで閉じるもの
であることがわかります。
サンプル画像
いくつかAppleが出しているiOSアプリのnavigationBarのスクショを掲載します。
サンプル画像を見てわかること
キャンセルボタンと完了を表すボタンのみ
どの画面にもキャンセルまたは完了(を意味するボタン)がついています。
完了と同じ意味なら「完了」でなくていい
完了の他にも「追加」「投稿」など完了の意味を内包していれば完了を使わなくても良さそうなのが読み取れます。
アイコンは使っていない
僕は✖️のアイコンとかも使用すると勝手に思ってたんですが、Appleのアプリには存在しなさそうな雰囲気を感じます。
また、完了に似た「閉じる」のような文言も使ってないようです。キャンセルはfontWeightがregular
タスクを完了することが目的なのでタスクを完了せず離脱する場合はregularみたいです。
完了を表すボタンはfontWeightがbold
逆に完了する場合はboldを使うのが推奨されるみたいです。
コードベースで見てみる
Documentでもこれまでの話と同じことがより簡潔に述べれられています。(この部分はmodalだけの話ではないです)
)なのでこれを実行すると下の画像のようにdone(完了とか)では勝手にboldになるし、plain(キャンセルとか)ではregularになります。
自分の作成してるiOSアプリや普段利用しているアプリがどうなってるのか見てみると面白いかもしれません。
- 投稿日:2020-02-16T23:47:37+09:00
[tips]Checkra1n脱獄時にフリーズしたときの対処法
脱獄してみた
ios13の脱獄ツールがリリースされたそうなので、脱獄してみた。
詳しい方から便利Tweaksをたくさん教えていただいて、非常に助かりました。
が、調子に乗っていろいろインストールしまくってたらrespring後にロック画面でフリーズした。症状
Tweakインストール後のrespringでフリーズ。ロック画面をカスタムするTweakだったのでおそらく対応してないiosのバージョンをインストールしてしまった。selioから該当するものをアンインストールしたいが、ロック画面でフリーズしてしまうのでそこまでたどり着けない。入獄するとcheckra1nがないのでsileoをいじれない。
対処法
どうしようかと調べたところ、checkra1nにはセーフモードで起動できるオプションがあるらしい。
checkra1nを起動→Options→Safe Modeにチェックを入れる
checkra1nがある状態でセーフモード起動できるので、sileoで該当するTweakをアンインストール。
そしてSafe Modeのチェックを外して再脱獄。できた。
まとめ
まだios13のjailbreakは歴史が浅いので対応しているTweakも少ないっぽい。
とりあえずインストールしてみるときはセーフモードでやるといいですね。みなさまよい脱獄lifeを♡
- 投稿日:2020-02-16T22:55:06+09:00
iPhoneとAIでマスクしている人を判別する
現在コロナウィルスが猛威を奮っております。ITパワーで何かできないものでしょうか。
開発環境
- XCode iOS Swift
- Vision Framework
iOSで顔の検出ができます。- CoreML Framework
iOSで学習モデルを扱います。- Turi Create
MACで学習モデルを作成します。画像判別のまとめ
機械学習にはモデルが必要です。Inception v3という公開モデルには1000個のカテゴリーが入っています。マウスであったり、コンピューターキーボードであったり、PCモニターの前に座った人をバーバーと判別したりします。
結果は、全てのカテゴリーを足して100になるように出ます。ABCDEの5つある場合、A80、B10、C10、D0、E0、のように「強いて言えばどれか」という結果になります。
なので、特定のモノを判別させたければ、違うモノを判別する必要があります。顔検出と顔判別は以前の記事にあります。
iPhoneで顔認識 - Qiita学習モデルの作成
今回はマスクするしないの2つだけです。結果はMask80%、None20%のようになります。MaskフォルダとNoneフォルダに顔写真をいっぱい入れて、pythonを実行して学習モデルを作成します。
model.pyimport turicreate as tc import os data = tc.image_analysis.load_images('train', with_path=True, recursive=True) data['label'] = data['path'].apply(lambda path: os.path.basename(os.path.dirname(path))) data.save('Chicken.sframe') train_data, test_data = data.random_split(0.9) model = tc.image_classifier.create(train_data, target='label', max_iterations=100) predictions = model.predict(test_data) metrics = model.evaluate(test_data) print('metrics=' + str(metrics['accuracy'])) model.save('Chicken.model') model.export_coreml('Chicken.mlmodel')実験
全画面を判別するよう修正しました。
日本人限定、女性限定、正面限定、白マスク限定(試行錯誤しました)
一応下のサンプルはフリー素材使っています。
結果
マスク判定が99なのがおかしいです。不明なものはすべてマスクになっている模様。作業の途中で悟りました。もっと他にいい方法があるはず。ソース
動かすにはmodelを作成する必要があります。
https://github.com/koji4104/VisionCoreMLまとめ
iPhoneで画像の機械学習を行うアプリは、いろいろ作り直して今回で3つ目です。判別しやすい画像や、トレーニング画像の集め方など、少しづつコツが掴めたと思います。
今回あまりうまくいきませんでしたけど、仮にマスクの判別がうまくいったとして、それからどうするかも課題です。アプリ側で一般的なCMSと連携できる機能があればいいかなと思います。
- 投稿日:2020-02-16T22:42:28+09:00
【Swift5】UnitテストでArray(配列)のMatchableを作った時の備忘録
はじめに
Cuckooを使ったUnitテストで、
Array(配列)のMatchableを作成した際の備忘録を残しておきます。
なお、誤った記述等ございましたら、コメントをいただけますと幸いです。実際に使用した用途・目的
Bluetoothクラスの実装で、
discoverCharacteristics(_:for:)メソッド
のテストコードを書く際に、このメソッドの引数である[CBUUID]?をMatchableに適合する必要がありました。CBUUIDは、Bluetoothで使用する特殊な型です。
CBUUIDの使い所については【Swift5】Bluetoothクラス実装の備忘録にも記載しておりますので、宜しければご覧ください。エラー内容
Argument type '[CBUUID]' does not conform to expected type 'OptionalMatchable'実際にテストした内容
discoverCharacteristics(_:for:)メソッド
を呼んだ際に、引数のCBUUIDに適切な値がセットされて呼ばれているのかをテストします。_ = XCTContext.runActivity(named: "Peripheral.discoverCharacteristics is called.", block: { _ in let uuidArray: [CBUUID] = [CBUUID(string: Const.Bluetooth.kUUID01), CBUUID(string: Const.Bluetooth.kUUID02), CBUUID(string: Const.Bluetooth.kUUID03)] verify(self.peripheral, times(1)).discoverCharacteristics(uuidArray, for: any()) })解決方法・コード
エラーの解消には以下の2つのextensionが必要でした。
MatchableExtensions.swiftextension Array: OptionalMatchable where Element == CBUUID { public var optionalMatcher: ParameterMatcher<[CBUUID]?> { return equal(to: self) } } extension CBUUID: OptionalMatchable { public var optionalMatcher: ParameterMatcher<CBUUID?> { return equal(to: self) } }今回は、CBUUID?の配列に適合する必要があったので、Arrayのextensionと、CBUUIDのextensionの2つが必要でした。
まとめ
Cuckooを使っているからといって特殊な対応が必要というわけではなく、
Arrayのextensionが作れれば問題なし!といった感じでした。応用も、基礎知識を持っていれば対応できるのですね・・・!
参考
- 投稿日:2020-02-16T21:15:35+09:00
iOS SimulatorでFlutter WebViewのlocalhost参照でページが真っ白になる件
Flutter WebViewでlocalhostのBasic認証ページを参照した際、ページが真っ白の状態で何も表示されない現象に遭遇した。
iOS Simulatorは13.2を使用。
[✓] Flutter (Channel stable, v1.9.1+hotfix.6, on Mac OS X 10.14.6 18G87, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.1) [✓] Xcode - develop for iOS and macOS (Xcode 11.2.1) [✓] Android Studio (version 3.5) [✓] VS Code (version 1.42.0) [✓] Connected device (2 available)結論から言うと、接続先を
localhost
としていたところを、ホストマシンのローカルループバックアドレス127.0.0.1
にすることで解消された。iOS SimulatorのSafari上で直接アクセスした際は問題なくページ参照できたが、FlutterアプリのWebView経由ではAndroid Emulatorと同じくIPアドレスに置き換える必要があるということなのだろうか。
Android Emulatorでnet::ERR_CONNECTION_REFUSEDが出た時の対処法 - QiitaStackOverflow等でそれらしい情報は得られたが、詳細仕様についてのドキュメントが見つからず…いまいちモヤる。
If you have a server running on the machine where you iOS simulator is running, then you need to choose 'http://127.0.0.1' as the URL.
swift - ios simulator access localhost server - Stack Overflow
- 投稿日:2020-02-16T20:45:41+09:00
UITextViewにPlaceHolderを折り返し有りで追加してみる
概要
UITextViewにプレースホルダーを追加してみました。
かつ、プレースホルダーには折り返しをしてくれるようにしてみました。はじめに
UITextViewにプレースホルダーを追加するだけのやり方であれば、
パイセン等が分かりやすくて素晴らしい記事を公開されてるので、そちらを参考にしてみてください。UITextViewにプレースホルダーを設定できるようにする(Swift4)
[Swift 4.2] UITextViewにプレースホルダーを追加する[iOS 12]私自身も大変参考にさせて頂きました?♂️
ありがとうございます?♂️?♂️?♂️
というか、ほぼほぼベースはパクらせてもらっこんなの
まずはじめに、単純にプレースホルダーをUILabelで追加しただけであれば、
こんな感じに折り返しが効かずに見切れてしまっています。※『UITextViewのPlaceHolderをここに表示してます。』という文言を設定
ソースコード
プレースホルダーの表示用として、UILabelをaddSubViewしており、
その際に、superViewであるUITextViewに対してAutolayoutの設定を入れることにより、折り返して表示してくれるようになってます。PlaceHolderTextView.swiftimport UIKit @IBDesignable class PlaceHolderTextView: UITextView { private let placeHolderLabelOriginPosition = CGPoint(x: 5.0, y: 8.0) lazy private var placeHolderLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 label.font = font label.backgroundColor = .clear label.translatesAutoresizingMaskIntoConstraints = false addSubview(label) let labelWidth = frame.width - placeHolderLabelOriginPosition.x * 2 NSLayoutConstraint.activate([ label.topAnchor.constraint(equalTo: topAnchor, constant: placeHolderLabelOriginPosition.y), label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: placeHolderLabelOriginPosition.x), label.widthAnchor.constraint(equalToConstant: labelWidth) ]) return label }() @IBInspectable var placeHolder: String = "" { didSet { reloadView() } } @IBInspectable var placeHolderColor: UIColor = .lightGray { didSet { reloadView() } } override var text: String! { didSet { changeVisiblePlaceHolder() } } override func awakeFromNib() { super.awakeFromNib() changeVisiblePlaceHolder() NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil) } } extension PlaceHolderTextView { private func reloadView() { placeHolderLabel.text = placeHolder placeHolderLabel.textColor = placeHolderColor changeVisiblePlaceHolder() } private func changeVisiblePlaceHolder() { placeHolderLabel.isHidden = placeHolder.isEmpty || !text.isEmpty ? true : false } @objc private func textChanged(notification: NSNotification?) { changeVisiblePlaceHolder() } }おわりに
そもそも、プレースホルダーに折り返しが必要になるほどの長文を書くシチュエーションに遭遇するのでしょうか・・・?
追記
@am10 さんからのご指摘&ご提案から、コードベースでテキスト代入時にもプレースホルダーの表示切替が可能になりました!
- 投稿日:2020-02-16T15:45:00+09:00
(at)PublishedがどのようにSwiftUIのViewを更新するのか
(at)PublishedがどのようにSwiftUIのViewを更新するのか
@Published
属性がどのようにSwiftUIのViewを更新するのかについて書いておきます。WWDC19の動画を見るとそもそも
@Published
属性がまだ存在していないのと、その当時はObservableObject
型がBindableObject
型であったり、@ObservedObject
属性が@ObjectBinding
属性だったりするのでややこしいためです。私の結論としては
@Published
あると便利なんだけど、コードを書き換えてるときになぜこのプロパティに@Published
にしたんだろう、という感覚になりやすい。おとなしく手動でやっといたら意図も読みやすくて混乱しなかったのにという気持ちになりやすいからですかね。
@ObservedObject
属性として指定されたObservableObjectはSwiftUIにsubscribeされている
- Viewは
@ObservedObject
指定したObservableObject
のobjectWillChange
をsubscribeしている
- subscribeし発火されるとViewを更新するようにしている
ObservableObject
でobjectWillChange
を使い発火させればいい@Published
はプロパティのwillSetでobjectWillChange
を使い発火させるのを暗黙的に行っているobjectWillChangeとは何か
objectWillChange
はCombineで定義されているObservableObject
を見るとわかります。@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) public protocol ObservableObject : AnyObject { /// The type of publisher that emits before the object has changed. associatedtype ObjectWillChangePublisher : Publisher // 制約の追加 Swift4.1(SE-0157) = ObservableObjectPublisher // デフォルトで利用する型 where Self.ObjectWillChangePublisher.Failure == Never // 条件 /// A publisher that emits before the object has changed. var objectWillChange: Self.ObjectWillChangePublisher { get } }
- プロパティ
objectWillChange
はSelf.ObjectWillChangePublisher
を返す- associatedtypeの
ObjectWillChangePublisher
Publisher
プロトコルを制約として追加している- 言い換えると
ObservableObject
はパブリッシャーを保持している- デフォルトでは
ObservableObjectPublisher
クラスを使う
- さらにエラーは流さない
- おそらくカスタマイズした
Publisher
も使えるObservableObjectPublisherとは何か
Combineで用意された
ObservableObjectPublisher
クラスを確認してみます。/// The default publisher of an `ObservableObject`. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) final public class ObservableObjectPublisher : Publisher { /// The kind of values published by this publisher. public typealias Output = Void /// The kind of errors this publisher might publish. /// /// Use `Never` if this `Publisher` does not publish errors. public typealias Failure = Never public init() /// This function is called to attach the specified `Subscriber` to this `Publisher` by `subscribe(_:)` /// /// - SeeAlso: `subscribe(_:)` /// - Parameters: /// - subscriber: The subscriber to attach to this `Publisher`. /// once attached it can begin to receive values. final public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == ObservableObjectPublisher.Failure, S.Input == ObservableObjectPublisher.Output final public func send() }
- OutputはVoidなのでイベントは
(Void) -> Void
のクロージャ
.sink { (Void) -> Void in ... }
- SwiftUIのViewとしては更新されたら
body
が実行される
- 使用されるオブジェクトのプロパティは全て読みだされるので
- イベント時のOutputがVoidでも特に気にしない
- どのような場合にカスタマイズする?
- Outputを変更したい場合がある?
- 思いつかないな...
必ずしも
@Published
を使う必要はない
@Published
を使いたいのは次の優先度のとき
- Viewが
Binding
なオブジェクトを取り出してバインディングしたい(読み込みと書き込み)ObservableObject
の中でfilter
やflatMap
したいobjectWillChange.send()
を自動でやりたいRxSwiftのようなUIからストリームを作れる仕組みがないために、
Binding
での値のやり取りが楽なので@Published
を使いたい。本当はそれ一択かもしれない。SwiftUIガイドブック
BOOTHで電子書籍として販売しています。
https://booth.pm/ja/items/1829015
読者のターゲットは次のような人を想定しています
- SwiftUIのレンダリングシステムが自動で更新されるタイミングを知らない
- 更新のきっかけになる
$ObservedObject
の@Published
にもやもやしている- SwiftUIのViewが自動で更新されるタイミングやその範囲にもやもやしている
@State
や@Binding
、@ObservedObject
の違いがよくわからない
@Environment
と@EnvironmentObject
の違いもまだ知らない- $オペレータを雰囲気で使って雰囲気で修正している
- コンパイルエラーになるたびに修正している
- propertyWrapperとdynamicMemberLookupを理解して使っていない
SwiftUIでpropertyWrapperとdynamicMemberLookupがあやふやだなーという気持ちになったままコードを書くことも多いのではないかと思ってます。
SwiftUI触ってる人に質問ですけどpropertyWrapperとdynamicMemberLookupについて理解してSwiftUIを触ってる?それともあやふやなままコンパイラに都度注意されてる?
— imajô (@yimajo) February 9, 2020
- 投稿日:2020-02-16T13:48:05+09:00
【Swift5】カラー指定する方法のチートシート永久保存版
この記事を書いた理由
Swiftでのカラー指定の方法は、たくさんあります。
毎回毎回、やりたいカラー指定のやり方を調べるのがめんどくさかったため、自分でカラー指定のチートシートを作成しました。環境
ツール version Swift version 5.1.3 Xcode Version 11.3 (11C29) チートシート
- storyboardでカラー指定する方法
- UIColorのプロパティでカラー指定する方法
- UIColorでRGB(CGFloat型)でカラー指定する方法
- UIColorを無理やりRGBでカラー指定する方法
- UIColorを16進数のカラーコードでカラー指定する方法
- UIColorをRGBでカラー指定する方法
- カラーリテラルを使ってカラー指定する方法
- Assets.xcassetsで色を管理し、カラー指定する方法
他のチートシート
- 投稿日:2020-02-16T12:13:28+09:00
SwiftUI VStackこういうことだったのか劇場
背景
SwiftUIにて画面作成するときにほぼ間違いなくお世話になる、VStack、HStackについて、その公式ドキュメントのリファレンスと実装に差異があり戸惑いました。そのときの疑問とそれをどのように整理して解消したのかを記録したものです。
対象とする読者
Swift初心者
SwiftUI初心者環境
私がこの記事を書いている際に利用しているのは次の環境です。
Xcode 11.3.1
Swift 5.1はじめに
VStack、HStackは、SwiftUIで画面レイアウトを作るときにほぼ間違いなくお世話になるもので、ビュー(ここでいうビューは、ボタンやテキスト等のUIコントロール部品や、それらをグループ化した子ビューなど広義でのUIコントロール全般を指すものとご理解ください)をレイアウトとして配置するためのコンテナーとして利用され、それ自体もビューとなります。
ビューを縦(垂直)方向に並べるときに利用するのが、VStack
ビューを横(水平)方向に並べるときに利用するのが、HStack
ビューを奥行き方向に並べるときに利用するのが、ZStack入れ子でこれらを使い分けることで、様々なレイアウトにカスタマイズして画面を構成していきます。
今回は、このうちのVStackを取り上げます。
VStack
イニシャライザの呼び出し方
イニシャライザとは、インスタンスの初期化用メソッドのこと。
公式ドキュメントの記載は次のとおりです。
公式ドキュメント(VStack)
https://developer.apple.com/documentation/swiftui/vstack// Creating a Stack init (alignment: HorizontalAlignment, spacing: CGFloat?, @ViewBuilder content: () -> Content)VStackイニシャライザのパラメータは3つあります。
alignment
水平方向の配置位置をHorizontalAlignment構造体のタイププロパティで指定。.leadeing
(左寄せ),.center
(中央寄せ),.trailing
(右寄せ)等の指定が可能。デフォルトは.center
spacing
子ビュー同士の間隔を調整するための余白を指定します。デフォルトはnil
でシステムデフォルトの余白が付与されます。content
このスタックに並べるビュー(子ビュー、ボタン、テキストなど)をまとめたものを返すクロージャを指定します。デフォルトのあるパラメータは省略可能です。一番最後の
content
だけは必ず指定が必要です。実際の使い方
VStack(alignment: .leading, spacing: 10) { Text("Hello World!") Text("Here We Go!") }もし私のようなSwift初心者は、ここで少し混乱します。
- 疑問1 イニシャライザの定義だと引数が3つあるけど、そのうちの最後のcontentが欠落している?
- 疑問2 その代わりに、{}内にVStack内に表示させたいビュー(Text2つ)を記述している?
- 疑問3 そもそも{}内にビュー(Text2つ)を改行して列挙してだけのようだけど、こういう書き方していいの?
では、これらの疑問を見ていきます。
疑問1と疑問2について
swiftでは「トレーリングクロージャー式」という記述をすることが可能です。
トレーリングは後方を指し、クロージャー式は{}で囲んだ式のこと。トレーリングクロージャーというのは、以下のようなSwift独自の構文です。
関数の最後の引数としてクロージャー式を渡す必要があり、クロージャー式が長い場合に、それを関数の引数として記載せずに、関数呼び出しの括弧の後(末尾)にクロージャー式{ }として記述できる
つまり、さきほどの実装例はトレーリングクロージャー式で記述されていたということです。それを利用しなかった場合は次のよう書き換えることができます。
// トレーリングクロージャー式を使わないで書くと・・・ VStack(alignment: .leading, spacing: 10, content: { Text("Hello World!") Text("Here We Go!") } )この例では、たしかにイニシャライザの宣言になんとなく寄ってきました。クロージャーの中身が数行なので、読みにくいということもありません。
ですが、クロージャーに記述する行数が増えれば増えるほど、読みずらくなるのは容易に想像できますので、基本はトレーリングクロージャー式を利用して、重たい記述は引数とは別に記述すべきなのですね。とても合理的な考え方だと思います。
疑問3について
疑問1,疑問2 についてはコードの書き方として便利な式が用意されていることを知りました。しかし、疑問3の「そもそも{}内にビュー(Text2つ)を改行して列挙してだけのようだけど、こういう書き方していいの?」については、まだ解決していません。
ここで、改めてVStackのイニシャライザを見てみると、content引数には、
@ViewBuilder
属性が付与されていることがわかります。この属性はSwift5.1の新機能である「ファンクションビルダー」が利用されています。
この属性がついたクロージャー内の各行は、暗黙的に1行が1引数となって、ViewBuilder
のbuildBlock
メソッドに渡されます(暗黙的に内部で変換されます)。つまり、クロージャー内にTextを2行記述しましたが、実際は内部では以下のように2つの引数を受け取るbuildBlockメソッドに渡され、View型のインスタンスを返却してもらっているのです。
// ViewBuilderへの変換がされた後の実装はこんな感じ //(こちらの実装でも、もちろん表示されます) VStack(alignment: .leading, spacing: 10, content: ViewBuilder.buildBlock( Text("Hello World!"), Text("Here We Go!") )ここまで記述すると、ようやくイニシャライザの定義と完全に理論的な一致が確認でき、安心できます。
なお、ViewBuilderでは、VStack、HStackなどクロージャー内部に最大10行のViewまでを引数として置き換えてくれるbuildBlockメソッドが事前に用意されています。VStackに10行分のTextを記述しても正常に画面表示できますが、11行以上記述するとビルドエラーが発生します。時間がある方はぜひ実際に試してみてください。
さいごに
新しい関数(イニシャライザ)を見て、リファレンス(公式ドキュメント)を参照するのは開発者として基本的な行動です。ですが、実際のコードとリファレンスに乖離があると、混乱したり正しい理解の妨げになります。
私も今回のこの記事を自分で整理してアウトプットすることで、頭の中を整理できました。私のようなSwift初心者の方に、この記事が少しでも役立てば幸いです。
- 投稿日:2020-02-16T09:40:17+09:00
Flutterアプリのライフサイクル
はじめに
Flutterアプリのライフサイクルについてまとめています。Flutterのライフサイクルというと、
- アプリ (
AppLifecycleState
) ← 今回の内容- 画面 (
StatefulWidget
)の2種類がありますが、今回は上のアプリ自体のライフサイクルについての内容です。
StatefulWidget
のライフサイクルについては、Flutter StatefulWidgetのライフサイクルにまとめていますので、参照してください。アプリのライフサイクル一覧とプラットフォームの対応関係
AppLifecycleState
の状態遷移と状態一覧を以下に示します。
基本的にプラットフォーム側の状態をFlutterアプリの状態として再定義している感じですが、iOSのライフサイクルに近いと思います。なお、iOSのライフサイクルは詳しくなく、AndroidエンジニアのためのiOSのUIViewControllerのライフサイクルとAndroidのActivityのライフサイクル比較を参考にさせていただきました。間違いがあればコメントでご指摘いただけると幸いです。
inactive
とpaused
の違いは、画面が表示されているか否かの違いの様子です。
状態 内容 Android iOS inactive アプリは表示されているが、フォーカスがあたっていない状態 onStart, onPause viewDidLoad paused アプリがバックグラウンドに遷移し(最前面に表示されてない)、入力不可な一時停止状態 onPause viewWillDisappear viewDidDisappear resumed アプリがフォアグランドに遷移し(paused状態から復帰)、復帰処理用の状態 onResume viewWillAppear viewDidAppear detached アプリが終了する時に通る終了処理用の状態 onDestroy viewWillDisappear AndroidのonSaveInstanceStateの様な仕組みはあるのか?
現状なさそうです。
AppLifecycleState
の状態を見て、必要であれば自分で対応する必要があります。状態遷移ユースケース
いくつかのユースケースで状態遷移の動作を確認してみました。
WidgetsBindingObserverを利用するとAppLifecycleStateの状態が取得出来ます。
その他、SystemChannels.lifecycle (詳細はこちらにまとめてます。) を利用しても取得可能です。確認のためのソースコード
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { AppLifecycleState _state; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { print('state = $state'); }アプリ起動
イベント発生なし
画面回転
イベント発生なし
アプリ終了
I/flutter (13730): state = AppLifecycleState.inactive I/flutter (13730): state = AppLifecycleState.paused I/flutter (13730): state = AppLifecycleState.detachedホームボタンを押す
I/flutter (13730): state = AppLifecycleState.inactive I/flutter (13730): state = AppLifecycleState.paused画面OFF
I/flutter (15366): state = AppLifecycleState.inactive I/flutter (15366): state = AppLifecycleState.pausedアプリを履歴から復帰
I/flutter (13730): state = AppLifecycleState.resumed画面分割で起動
I/flutter (13252): state = AppLifecycleState.inactive画面分割状態でサブ画面 (下画面) のアプリを終了
I/flutter (13252): state = AppLifecycleState.inactive画面分割状態でサブ画面 (下画面) で新しいアプリを洗濯して、画面を2分割に戻る
I/flutter (13252): state = AppLifecycleState.resumed参考文献