Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 705 Vote(s) - 3.55 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Implement dark mode switch in SwiftUI App

#1
I'm currently looking into Dark Mode in my App. While Dark Mode itself isn't much of a struggle because of my SwiftUI basis i'm struggling with the option to set the ColorScheme independent of the system ColorScheme.


[I found this in apples human interface guidelines][1] and i'd like to implement this feature. (Link: [Human Interface Guidelines][2])

Any idea how to do this in SwiftUI? I found some hints towards `@Environment` but no further information on this topic. (Link: [Last paragraph][3])


[1]:

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply

#2
Using @AppStorage

Initial app launch code

Make sure that **appearanceSwitch** is an optional **ColorScheme?** so **.none** can can be selected

```
import SwiftUI

@main
struct AnOkApp: App {

@AppStorage("appearanceSelection") private var appearanceSelection: Int = 0

var appearanceSwitch: ColorScheme? {
if appearanceSelection == 1 {
return .light
}
else if appearanceSelection == 2 {
return .dark
}
else {
return .none
}
}

var body: some Scene {
WindowGroup {
AppearanceSelectionView()
.preferredColorScheme(appearanceSwitch)
}
}
}
````

Selection View

````
import SwiftUI

struct AppearanceSelectionView: View {

@AppStorage("appearanceSelection") private var appearanceSelection: Int = 0

var body: some View {
NavigationView {
Picker(selection: $appearanceSelection) {
Text("System")
.tag(0)
Text("Light")
.tag(1)
Text("Dark")
.tag(2)
} label: {
Text("Select Appearance")
}
.pickerStyle(.menu)
}
}
}
Reply

#3
Let's try to make life easier,

In your initial app launch code, add the following,
```
@main
struct MyAwesomeApp: App {
@AppStorage("appearance") var appearance: String = "system"

var body: some Scene {
WindowGroup {
StartView()
.preferredColorScheme(appearance == "system" ? nil : (appearance == "dark" ? .dark : .light))
}
}
}
```

Now you can make a separate setting AppearanceView and can do the following,
```
struct AppearanceView: View {
@AppStorage("appearance") var appearance: String = "system"

var body: some View {
VStack {
ScrollView(showsIndicators: false) {
ForEach(Appearance.allCases, id: \.self) { appearance in
Button {
self.appearance = appearance.rawValue
} label: {
HStack {
Text(LocalizedStringKey(appearance.rawValue))

Spacer()

Image(self.appearance == appearance.rawValue ? "check-selected" : "check-unselected")
}
}
}
}
}
}

}
```

And the Appearance enum,
```
enum Appearance: String, CaseIterable {
case light = "light"
case dark = "dark"
case system = "system"
}
```
Reply

#4
I've used the answer by [@Arturo][1] and combined in some of the work by [@multitudes][2] to make my own implementation

I still add @main as well as in my settings view

ContentView()
.modifier(DarkModeViewModifier())

I then have the following:

class AppThemeViewModel: ObservableObject {
@AppStorage("appThemeSetting") var appThemeSetting = Appearance.system
}

struct DarkModeViewModifier: ViewModifier {
@ObservedObject var appThemeViewModel: AppThemeViewModel = AppThemeViewModel()

public func body(content: Content) -> some View {
content
.preferredColorScheme((appThemeViewModel.appThemeSetting == .system) ? .none : appThemeViewModel.appThemeSetting == .light ? .light : .dark)
}
}

enum Appearance: String, CaseIterable, Identifiable {
case system
case light
case dark
var id: String { self.rawValue }
}

struct ThemeSettingsView:View{
@AppStorage("appThemeSetting") var appThemeSetting = Appearance.system

var body: some View {
HStack {
Picker("Appearance", selection: $appThemeSetting) {
ForEach(Appearance.allCases) {appearance in
Text(appearance.rawValue.capitalized)
.tag(appearance)
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
}

working almost perfectly - the only issue I have is when switching from a user selected value to system setting it doesn't update the settings view itself. When switching from system to Dark/Light or between Dark and light it settings screen does update fine.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

Reply

#5
# Systemwide with SwiftUI with SceneDelegate lifecycle
I used the hint provided in the answer by [in the answer by Mojtaba Hosseini][1] to make my own version in SwiftUI (App with the AppDelegate lifecycle). I did not look into using iOS14's @main instead of SceneDelegate yet.

Here is a link to the GitHub repo. The example has light, dark, and automatic picker which change the settings for the whole app.
And I went the extra mile to make it localizable!

[GitHub repo][2]

I need to access the `SceneDelegate` and I use the same code as Mustapha with a small addition, when the app starts I need to read the settings stored in UserDefaults or @AppStorage etc.
Therefore I update the UI again on launch:

```swift
private(set) static var shared: SceneDelegate?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Self.shared = self

// this is for when the app starts - read from the user defaults
updateUserInterfaceStyle()
}
```

The function `updateUserInterfaceStyle()` will be in `SceneDelegate`.
I use an extension of UserDefaults here to make it compatible with iOS13 (thanks to [twanni][3]!):

```swift
func updateUserInterfaceStyle() {
DispatchQueue.main.async {
switch UserDefaults.userInterfaceStyle {
case 0:
self.window?.overrideUserInterfaceStyle = .unspecified
case 1:
self.window?.overrideUserInterfaceStyle = .light
case 2:
self.window?.overrideUserInterfaceStyle = .dark
default:
self.window?.overrideUserInterfaceStyle = .unspecified
}
}
}
```

This is consistent with the [apple documentation for `UIUserInterfaceStyle`][4]

Using a picker means that I need to iterate on my three cases so I made an enum which conforms to identifiable and is of type `LocalizedStringKey` for the localisation:

```swift
// check LocalizedStringKey instead of string for localisation!
enum Appearance: LocalizedStringKey, CaseIterable, Identifiable {
case light
case dark
case automatic

var id: String { UUID().uuidString }
}
```

And this is the full code for the picker:
```swift

struct AppearanceSelectionPicker: View {
@Environment(\.colorScheme) var colorScheme
@State private var selectedAppearance = Appearance.automatic

var body: some View {
HStack {
Text("Appearance")
.padding()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Picker(selection: $selectedAppearance, label: Text("Appearance")) {
ForEach(Appearance.allCases) { appearance in
Text(appearance.rawValue)
.tag(appearance)
}
}
.pickerStyle(WheelPickerStyle())
.frame(width: 150, height: 50, alignment: .center)
.padding()
.clipShape(RoundedRectangle(cornerRadius: 20, style: .continuous))
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
.padding()

.onChange(of: selectedAppearance, perform: { value in
print("changed to ", value)
switch value {
case .automatic:
UserDefaults.userInterfaceStyle = 0
SceneDelegate.shared?.window?.overrideUserInterfaceStyle = .unspecified
case .light:
UserDefaults.userInterfaceStyle = 1
SceneDelegate.shared?.window?.overrideUserInterfaceStyle = .light
case .dark:
UserDefaults.userInterfaceStyle = 2
SceneDelegate.shared?.window?.overrideUserInterfaceStyle = .dark
}
})
.onAppear {
print(colorScheme)
print("UserDefaults.userInterfaceStyle",UserDefaults.userInterfaceStyle)
switch UserDefaults.userInterfaceStyle {
case 0:
selectedAppearance = .automatic
case 1:
selectedAppearance = .light
case 2:
selectedAppearance = .dark
default:
selectedAppearance = .automatic
}
}
}
}
```

The code `onAppear` is there to set the wheel to the correct value when the user gets to that settings view. Every time that the wheel is moved, through the `.onChange` modifier, the user defaults are updated and the app changes the settings for all views through its reference to the `SceneDelegate`.

(A gif is on the GH repo if interested.)


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

[4]:

[To see links please register here]

Reply

#6
The answer from @ADB is good, but I found a better one. Hopefully someone finds even a better one than mine :D
This approach doesn't call the same function over and over again once the app switches state (goes to the background and comes back)

in your `@main` view add:

ContentView()
.modifier(DarkModeViewModifier())


Now create the `DarkModeViewModifier()` ViewModel:

class AppThemeViewModel: ObservableObject {

@AppStorage("isDarkMode") var isDarkMode: Bool = true // also exists in DarkModeViewModifier()
@AppStorage("appTintColor") var appTintColor: AppTintColorOptions = .indigo

}

struct DarkModeViewModifier: ViewModifier {
@ObservedObject var appThemeViewModel: AppThemeViewModel = AppThemeViewModel()

public func body(content: Content) -> some View {
content
.preferredColorScheme(appThemeViewModel.isDarkMode ? .dark : appThemeViewModel.isDarkMode == false ? .light : nil)
.accentColor(Color(appThemeViewModel.appTintColor.rawValue))
}
}
Reply

#7
```
#SwiftUI #iOS #DarkMode #ColorScheme

//you can take one boolean and set colorScheme of perticuler view accordingly such like below

struct ContentView: View {

@State var darkMode : Bool = false

var body: some View {
VStack {
Toggle("DarkMode", isOn: $darkMode)
.onTapGesture(count: 1, perform: {
darkMode.toggle()
})
}
.preferredColorScheme(darkMode ? .dark : .light)

}
}



// you can also set dark light mode of whole app such like below

struct ContentView: View {
@State var darkMode : Bool = false

var body: some View {
VStack {
Toggle("DarkMode", isOn: $darkMode)
.onTapGesture(count: 1, perform: {
darkMode.toggle()
})
}
.onChange(of: darkMode, perform: { value in
SceneDelegate.shared?.window?.overrideUserInterfaceStyle = value ? .dark : .light
})

}
}
```
Reply

#8
[@Mojtaba Hosseini's][1] answer really helped me with this, but I'm using iOS14's `@main` instead of `SceneDelegate`, along with some `UIKit` views so I ended up using something like this (this doesn't toggle the mode, but it does set dark mode across `SwiftUI` and `UIKit`:
```swift
@main
struct MyTestApp: App {

@Environment(\.scenePhase) private var phase

var body: some Scene {
WindowGroup {
ContentView()
.accentColor(.red)
.preferredColorScheme(.dark)
}
.onChange(of: phase) { _ in
setupColorScheme()
}
}

private func setupColorScheme() {
// We do this via the window so we can access UIKit components too.
let window = UIApplication.shared.windows.first
window?.overrideUserInterfaceStyle = .dark
window?.tintColor = UIColor(Color.red)
}
}
```


[1]:

[To see links please register here]

Reply

#9
# Single View
To change the color scheme of a single view (Could be the main `ContentView` of the app), you can use the following modifier:
```
.environment(\.colorScheme, .light) // or .dark
```
or
```
.preferredColorScheme(.dark)
```

Also, you can apply it to the `ContentView` to make your entire app dark!

<sub>Assuming you didn't change the `ContentView` name in scene delegate or `@main`</sub>

---
# Entire App (Including the `UIKit` parts and The `SwiftUI`)

First you need to access the window to change the app colorScheme that called `UserInterfaceStyle` in `UIKit`.

I used this in `SceneDelegate`:

private(set) static var shared: SceneDelegate?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
Self.shared = self
...
}

Then you need to bind an action to the toggle. So you need a model for it.
```
struct ToggleModel {
var isDark: Bool = true {
didSet {
SceneDelegate.shared?.window!.overrideUserInterfaceStyle = isDark ? .dark : .light
}
}
}
```
At last, you just need to toggle the switch:
```
struct ContentView: View {
@State var model = ToggleModel()

var body: some View {
Toggle(isOn: $model.isDark) {
Text("is Dark")
}
}
}
```

---
# From the UIKit part of the app

Each `UIView` has access to the window, So you can use it to set the `. overrideUserInterfaceStyle` value to any scheme you need.
```
myView.window?.overrideUserInterfaceStyle = .dark
```
Reply

#10
A demo of using `@AppStorage` to switch dark mode


PS: For global switch, **modifier should be added to WindowGroup/MainContentView**

```swift
import SwiftUI

struct SystemColor: Hashable {
var text: String
var color: Color
}

let backgroundColors: [SystemColor] = [.init(text: "Red", color: .systemRed), .init(text: "Orange", color: .systemOrange), .init(text: "Yellow", color: .systemYellow), .init(text: "Green", color: .systemGreen), .init(text: "Teal", color: .systemTeal), .init(text: "Blue", color: .systemBlue), .init(text: "Indigo", color: .systemIndigo), .init(text: "Purple", color: .systemPurple), .init(text: "Pink", color: .systemPink), .init(text: "Gray", color: .systemGray), .init(text: "Gray2", color: .systemGray2), .init(text: "Gray3", color: .systemGray3), .init(text: "Gray4", color: .systemGray4), .init(text: "Gray5", color: .systemGray5), .init(text: "Gray6", color: .systemGray6)]

struct DarkModeColorView: View {

@AppStorage("isDarkMode") var isDarkMode: Bool = true

var body: some View {
Form {
Section(header: Text("Common Colors")) {
ForEach(backgroundColors, id: \.self) {
ColorRow(color: $0)
}
}
}
.toolbar {
ToolbarItem(placement: .principal) { // navigation bar
Picker("Color", selection: $isDarkMode) {
Text("Light").tag(false)
Text("Dark").tag(true)
}
.pickerStyle(SegmentedPickerStyle())
}
}
.modifier(DarkModeViewModifier())
}
}

private struct ColorRow: View {

let color: SystemColor

var body: some View {
HStack {
Text(color.text)
Spacer()
Rectangle()
.foregroundColor(color.color)
.frame(width: 30, height: 30)
}
}
}

public struct DarkModeViewModifier: ViewModifier {

@AppStorage("isDarkMode") var isDarkMode: Bool = true

public func body(content: Content) -> some View {
content
.environment(\.colorScheme, isDarkMode ? .dark : .light)
.preferredColorScheme(isDarkMode ? .dark : .light) // tint on status bar
}
}

struct DarkModeColorView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
DarkModeColorView()
}
}
}
```

[![enter image description here][1]][1]

[1]:

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through