- 投稿日:2020-03-14T23:25:39+09:00
Vue.js、Capacitorでアプリ開発
ウェブ開発ノウハウを利用してスマートフォンアプリの開発を行うハイブリッドアプリについて、ソリューションの一つとしてCapacitor+Vue.jsを考えてみました。
メリットは、最初のプロジェクトの作成が非常に簡単にできることですので、プロジェクトの作成手順を紹介します。Vue.jsプロジェクトの作成
https://qiita.com/jjjjjj/items/a43922631f1350bb9c65
を参照Capacitorのインストール
以下を参考にしました。
https://capacitor.ionicframework.jp/docs/getting-started$ npm install --save @capacitor/core @capacitor/cliCapacitor初期設定
$ npx cap init
index.htmlファイルの場所を変更
capacitor.config.jsonのwebDirをwwwからdistに変更する。
プラットフォームのインストール
$ npx cap add ios $ npx cap add androidVueプロジェクトをネイティブにコピーする
$ npx cap copy ios $ npx cap copy androidIDEを開いてビルド、実行する
$ npx cap open ios $ npx cap open android
- 投稿日:2020-03-14T16:00:37+09:00
新しいプログラムファイルの作り方
- 投稿日:2020-03-14T04:08:14+09:00
【SwiftUI】CustomViewを用いて不要なViewの再描画を避ける
SwiftUIは
宣言的に画面のコードを書くことができるため
UIKitと比べても可読性が高く扱いやすくなっていると感じています。一方で
現時点では足りない機能(CollectionView的なものがないなど)や
動作がいまいちだなと感じる部分もあるかと思います。その中の一つに
@State
などを用いた際に
値を変更する度に画面全体を再描画してしまうことで
変化のないViewも再描画してしまうという問題があります。今回は
Viewが何を基準に行われ
必要ないViewの再描画をどう回避できるのかについて
下記の記事を参考に確認してみました。サンプルコード
下記のコードを使用します。
struct SampleView: View { var texts = ["SwiftUI", "A new declarative UI framework", "Let us try to understand how SwiftUI renders its views"] @State var selected = 0 @State var textFieldValue = "" @State var isSwitchOn = false var body: some View { VStack(spacing: 16) { TextField("Enter your name", text: $textFieldValue) .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) .background(Color(UIColor.secondarySystemFill)) .cornerRadius(4) Toggle(isOn: $isSwitchOn, label: { Text("A simple Switch") }) Picker(selection: $selected, label: Text(""), content: { Text("Word").tag(0) Text("Phrase").tag(1) Text("Sentence").tag(2) }).pickerStyle(SegmentedPickerStyle()) HStack { Text(texts[selected]) Spacer() Button(action: { print("just checking") }, label: { Image(systemName: "plus.circle.fill") .foregroundColor(.blue) }) } }.padding()} }検証内容
- 全てのViewにbreakpointを設定します。
- アプリを起動して、それぞれのViewを操作してみます。
ここからわかることは
TextField
、Toggle
、Button
に操作に加えても
breakpointに止まりませんが
Picker
の値を変更すると
全てのbreakpointに止まります。
つまりbody
全体が再描画されていることがわかります。原因
参考にしている記事によると
SwiftUIは親Viewの変数が(今回はselected
)
子Viewに影響を与えるかどうか自体は把握しているものの
どのViewに影響を与えているかまでは把握していないため
body
全体を再描画しているということだそうです。
(この記事を書いているのは2020/03/14です。)実際に
Text(texts[selected])
の部分を
Text("")
に変更すると
body
全体の再描画は発生しません。
(SwiftUIはPicker
の変更がどこにも影響しないことがわかっているからです。)解決策
CustomViewを定義することで
再描画されることを回避することができます。例えば一番最初の
TextField
の部分を
CustomViewに切り出します。struct SampleView: View { ... var body: some View { VStack(spacing: 16) { MyTextField(placholder: "Enter your name", value: $textFieldValue) ... }.padding()} } struct MyTextField: View { var placholder: String @Binding var value: String var body: some View { TextField(placholder, text: $value) .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) .background(Color(UIColor.secondarySystemFill)) .cornerRadius(4) } }これで上記の検証内容を実行してみます。
すると
Picker
の値を変更した際に
MyTextField
のbreakpointには止まりません。なぜ?
SwiftUIは
Equatable
に適合しているViewを見て
body
に変更があるかどうか見ており
Picker
に変更があっても
MyTextField
のbody
には変更がないため
再描画が発生しなかったということだそうです。※
TextField
自体はEquatable
に適合していませんが@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) public struct TextField<Label> : View where Label : View { /// Declares the content and behavior of this view. public var body: some View { get } /// The type of view representing the body of this view. /// /// When you create a custom view, Swift infers this type from your /// implementation of the required `body` property. public typealias Body = some View }今回は
TextField
のLabel
がText
で
Text
はEquatable
に適合しているため
その差分を比較しているのではないかと思っています。/// A view that displays one or more lines of read-only text. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) @frozen public struct Text : Equatable { /// Creates an instance that displays `content` verbatim. @inlinable public init(verbatim content: String) /// Creates an instance that displays `content` verbatim. public init<S>(_ content: S) where S : StringProtocol /// Creates text that displays localized content identified by a key. /// /// - Parameters: /// - key: The key for a string in the table identified by `tableName`. /// - tableName: The name of the string table to search. If `nil`, uses /// the table in `Localizable.strings`. /// - bundle: The bundle containing the strings file. If `nil`, uses the /// main `Bundle`. /// - comment: Contextual information about this key-value pair. public init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil) /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. public static func == (a: Text, b: Text) -> Bool }ここら辺いまいち不明瞭なので
もしご存知の方がいらっしゃいましたら
教えていただけるとうれしいです??♂️変更の影響がある部分を切り出す
上記の
MyTextField
のようにToggle
の部分を切り出すことで
Toggle
もPicker
の変更で再描画しなくなります。しかし
これだとViewが追加されるたびに
CustomViewを追加していくことになります。
(可読性などを考えるとそれは良い選択かもしれませんが)そうである場合にも
あるViewの変更が
関係のないViewにまで影響を及ぼすのは
あまり良いとは思われません。そこでお互いに影響を与える部分(
Picker
とHStack
)を切り出してみます。struct SampleView: View { ... var body: some View { VStack(spacing: 16) { // 確認のため最初の形に戻しています TextField("Enter your name", text: $textFieldValue) .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16)) .background(Color(UIColor.secondarySystemFill)) .cornerRadius(4) ... MyPickerTextField() }.padding()} } struct MyPickerTextField: View { var texts = ["SwiftUI", "A new declarative UI framework", "Let us try to understand how SwiftUI renders its views"] @State var selected = 0 var body: some View { VStack(spacing: 16) { Picker(selection: $selected, label: Text(""), content: { Text("Word").tag(0) Text("Phrase").tag(1) Text("Sentence").tag(2) }).pickerStyle(SegmentedPickerStyle()) HStack { Text(texts[selected]) Spacer() Button(action: { print("just checking") }, label: { Image(systemName: "plus.circle.fill") .foregroundColor(.blue) }) } } } }ここで再び検証内容を実行してみます。
するとbreakpointは
MyPickerTextField
の中でしか止まらなくなり
親Viewのbody
全体の再描画は発生しなくなったことがわかりました。結果として変更の影響範囲を必要な部分にだけに抑えることができました。
まとめ
SwiftUIの再描画について見てみました。
Viewが複雑になってくると
body
全体の再描画を繰り返し行うことで
パフォーマンスに大きな影響を与える可能性も出てきます。Viewの表示速度などで
気になる部分がある場合
こういう点を調査してみるのも何かの解決につながるかもしれませんね?何か間違いなどございましたら
ご指摘いただけますとうれしいです??♂️