はじめに

NativeScript(NativeScript-Vue)で Dark Mode(iOS), Dark Theme(Android) に対応する方法。

TL;DR

  • Android: forceDarkAllowed で Dark Theme のサポート有無を設定
  • iOS: UIUserInterfaceStyle で Dark Mode を非サポートに設定可能(デフォルトで影響を受ける)
  • Dark Mode/Theme だと .ns-dark, Light だと .ns-lightroot-view に適用される
  • @nativescript/theme を使って Theme.setMode(Theme.Light) で強制変更もできる

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. 前置き
      1. Android
      2. iOS
    2. テーマの強制指定
      1. iOS
      2. iOS, Android 共通
    3. ダークモード対応
      1. スタイル
      2. 現在の設定、設定変更の検出
  5. まとめ
  6. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.4
BuildVersion: 19E287

$ node -v
v12.7.0

$ npm -v
6.10.3

$ tns --version
6.4.0

$ grep -C1 version package.json
"tns-android": {
"version": "6.4.1"
},
"tns-ios": {
"version": "6.4.2"
}

$ tns plugin
Dependencies:
┌───────────────────────────────┬─────────┐
│ Plugin │ Version │
│ @nativescript/theme │ ^2.2.1 │
│ @vue/devtools │ ^5.0.6 │
│ nativescript-socketio │ ^3.2.1 │
│ nativescript-toasty │ ^1.3.0 │
│ nativescript-vue │ ^2.4.0 │
│ nativescript-vue-devtools │ ^1.2.0 │
│ tns-core-modules │ ^6.0.0 │
│ vuex │ ^3.1.1 │
└───────────────────────────────┴─────────┘
Dev Dependencies:
┌────────────────────────────────────┬─────────┐
│ Plugin │ Version │
│ @babel/core │ ^7.0.0 │
│ @babel/preset-env │ ^7.0.0 │
│ babel-loader │ ^8.0.2 │
│ nativescript-dev-webpack │ ^1.0.0 │
│ nativescript-vue-template-compiler │ ^2.0.0 │
│ nativescript-worker-loader │ ~0.9.0 │
│ node-sass │ ^4.9.2 │
│ vue-loader │ ^15.4.0 │
└────────────────────────────────────┴─────────┘

詳細

Dark Mode - NativeScript Docs にまとまっている。

前置き

Android

参考: Dark Theme For Android

Android 10(API level 29) 以降で Dark Theme をサポート。

NativeScript v6.2 以降のバージョンで作られたアプリだと、/app/App_Recoures/Android/src/main/res/values/styles.xmlforceDarkAllowed の設定がデフォルトでされている。

1
<item name="android:forceDarkAllowed">true</item>

forceDarkAllowed が無ければ Dark Theme の影響を受けない、というようにも読めるが詳細未確認。

iOS

参考: Dark Mode For iOS - Dark Mode - NativeScript Docs

NativeScript v6.2 より古いバージョンで作られたアプリだと Dark Mode の影響を受けない。

NativeScript v6.2 以降、Xcode 11 以上でビルド、iOS 13 以降だと Dark Mode の影響を受ける。

テーマの強制指定

iOS

参考: Dark Mode For iOS - Dark Mode - NativeScript Docs

ダークモードをオフにしたい(ひとまず非サポートにしたい)場合は app/App_Recoures/iOS/Info.plistUIUserInterfaceStyle の設定を追加。

1
2
3
4
5
6
7
8
9
  ...
<plist version="1.0">
<dict>
...
+ <key>UIUserInterfaceStyle</key>
+ <string>Light</string>
...
</dict>
</plist>

iOS, Android 共通

参考: ios - How to effectively disable Dark Mode theming in Nativescript? - Stack Overflow

以下コードを実行することで、テーマを強制指定できる。

1
2
import Theme from "@nativescript/theme";
Theme.setMode(Theme.Light);

NativeScript-Vue の場合は app/main.js で実行すれば良い。

1
2
3
4
5
6
7
8
9
import Vue from 'nativescript-vue'

import Theme from "@nativescript/theme";
Theme.setMode(Theme.Light);

import App from './components/App'
new Vue({
render: h => h('frame', [h(App)])
}).$start()

なお iOS は、前述の UIUserInterfaceStyleapp/App_Recoures/iOS/Info.plist に設定しておかないと、システム設定の Dark Mode 設定が優先される模様。

ダークモード対応

参考: How Does It Work

スタイル

Dark Mode だと .ns-dark が、Light Mode だと .ns-lightroot-view に適用される。

以下のようにすると、後述の画像のようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<Page>
<ActionBar title="Welcome to NativeScript-Vue!" />
<GridLayout columns="*" rows="*">
<Label class="message" text="Hello World!" col="0" row="0" />
</GridLayout>
</Page>
</template>

<style scoped lang="scss">
.message {
vertical-align: center;
text-align: center;
font-size: 20;
}

.ns-light {
.message {
color: #333333;
}

ActionBar {
background-color: #53ba82;
color: #ffffff;
}
}

.ns-dark {
.message {
color: #ffffff;
}

ActionBar {
color: #ffffff;
}
}
</style>

Light Mode

Dark Mode

現在の設定、設定変更の検出

また、systemAppearancesystemAppearanceChangedEvent イベントを使った処理もできる。

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'nativescript-vue'

const application = require("tns-core-modules/application");
application.on(application.systemAppearanceChangedEvent, (args) => {
console.log({ on: 'systemAppearanceChangedEvent', args });
});

import App from './components/App'
new Vue({
render: h => h('frame', [h(App)])
}).$start()

Android

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Dark -> Light
{
on: 'systemAppearanceChangedEvent',
args: {
eventName: 'systemAppearanceChanged',
android: {
constructor: [Object]
},
newValue: 'light', // *****
object: {
_observers: [Object],
callbacks: [Object],
_registeredReceivers: {},
_pendingReceiverRegistrations: [Object],
nativeApp: [Object],
packageName: 'com.jet.truckshop',
context: [Object],
startActivity: [Object],
_orientation: 'portrait',
_systemAppearance: 'light',
foregroundActivity: [Object],
paused: true
}
}
}

// Light -> Dark
{
on: 'systemAppearanceChangedEvent',
args: {
eventName: 'systemAppearanceChanged',
android: {
constructor: [Object]
},
newValue: 'dark', // *****
object: {
_observers: [Object],
callbacks: [Object],
_registeredReceivers: {},
_pendingReceiverRegistrations: [Object],
nativeApp: [Object],
packageName: 'com.jet.truckshop',
context: [Object],
startActivity: [Object],
_orientation: 'portrait',
_systemAppearance: 'dark',
foregroundActivity: [Object],
paused: true
}
}
}

iOS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Dark -> Light
{
on: 'systemAppearanceChangedEvent',
args: {
eventName: 'systemAppearanceChanged',
ios: {
_backgroundColor: {},
_observers: [Object],
_delegate: [Object],
_window: {},
_orientation: 'portrait',
_rootView: [Object],
_systemAppearance: 'light'
},
newValue: 'light', // *****
object: {
_backgroundColor: {},
_observers: [Object],
_delegate: [Object],
_window: {},
_orientation: 'portrait',
_rootView: [Object],
_systemAppearance: 'light'
}
}
}

// Light -> Dark
{
on: 'systemAppearanceChangedEvent',
args: {
eventName: 'systemAppearanceChanged',
ios: {
_backgroundColor: {},
_observers: [Object],
_delegate: [Object],
_window: {},
_orientation: 'portrait',
_rootView: [Object],
_systemAppearance: 'dark'
},
newValue: 'dark', // *****
object: {
_backgroundColor: {},
_observers: [Object],
_delegate: [Object],
_window: {},
_orientation: 'portrait',
_rootView: [Object],
_systemAppearance: 'dark'
}
}
}

まとめ

  • Android: forceDarkAllowed で Dark Theme のサポート有無を設定
  • iOS: UIUserInterfaceStyle で Dark Mode を非サポートに設定可能(デフォルトで影響を受ける)
  • Dark Mode/Theme だと .ns-dark, Light だと .ns-lightroot-view に適用される
  • @nativescript/theme を使って Theme.setMode(Theme.Light) で強制変更もできる

参考文献

関連記事