Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
DandyLyons committed Aug 7, 2024
1 parent 1b7d3e0 commit fd51b0d
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 28 deletions.
10 changes: 5 additions & 5 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
{
"originHash" : "e82ddf3c8b522cc05bade3671257a5decc3d2380201dd2fcac50426085dcec30",
"originHash" : "ad78593b472bc4772b7ec8a0b780098c3598cbbd5afa4ddfde6797a6720a71f8",
"pins" : [
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing.git",
"state" : {
"revision" : "e883fc9ea51e76dc9ed13fd4a92b0ee258a1e8c9",
"version" : "1.17.3"
"revision" : "6d932a79e7173b275b96c600c86c603cf84f153c",
"version" : "1.17.4"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd",
"version" : "510.0.1"
"revision" : "06b5cdc432e93b60e3bdf53aff2857c6b312991a",
"version" : "600.0.0-prerelease-2024-07-30"
}
}
],
Expand Down
8 changes: 6 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ let package = Package(
targets: ["PlusNightMode"]),
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.3")
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.3"),
// .package(url: "https://github.com/swiftlang/swift-docc.git", branch: "main"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "PlusNightMode",
dependencies: [
// .product(name: "SwiftDocC", package: "swift-docc"),
],
resources: [
.process("Resources/")
.process("Resources/"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
Expand Down
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ PlusNightMode makes it easy for your app to add user-configurable light mode, da
<img src="https://raw.githubusercontent.com/DandyLyons/PlusNightMode/main/README/Example.GIF">

## Sections
- [What is night mode?](#what-is-night-mode)
- [Usage](#usage)
- [ColorSchemeMode](#colorschememode)
- [Known Limitations](#known-limitations)
- [Design Considerations](#design-considerations)
- [Collaboration](#collaboration)
- [Thank Yous](#thank-yous)
- [PlusNightMode](#plusnightmode)
- [Sections](#sections)
- [What is night mode?](#what-is-night-mode)
- [Usage](#usage)
- [ColorSchemeMode](#colorschememode)
- [Known Limitations](#known-limitations)
- [Design Considerations](#design-considerations)
- [Collaboration](#collaboration)
- [Thank Yous](#thank-yous)

## What is night mode?

Expand All @@ -26,7 +28,7 @@ Simply add `.observingNightMode()` to the very top of your View hierarchy like s

```swift
struct NightModeView: View {
@State private var isNightModeOn = true // 👈🏼
@Environment(\.colorSchemeMode) var colorSchemeMode // 👈🏼

var body: some View {
NavigationStack {
Expand All @@ -46,7 +48,7 @@ struct NightModeView: View {
Text(string)
}
}
.observingNightMode(isNightModeOn) // 👈🏼
.colorSchemeMode($colorSchemeMode) // 👈🏼
}
}
```
Expand All @@ -56,17 +58,18 @@ Please note, presented views are not considered to be child views by SwiftUI. Th

```swift
struct ExampleView: View {
@State private var isPresenting = false
@Environment(\.colorSchemeMode) var colorSchemeMode

var body: some View {
Button {
isPresenting.toggle()
} label: {
Text("Present Sheet View")
}
.colorSchemeMode($colorSchemeMode)
.sheet(isPresented: $isPresenting) {
Text("Presented View")
.observingNightMode(true)
.colorSchemeMode($colorSchemeMode)
// 👆🏼 SwiftUI will not add Night Mode to
// presented views unless you explicitly add it
}
Expand Down Expand Up @@ -97,6 +100,23 @@ struct ExampleView: View {
- `light`: Light mode
- `auto`: Automatically adjust to the device's current light/dark mode setting.

For a user-configurable `ColorSchemeMode` that you would like to keep in sync across the whole app, try using the EnvironmentValue:
```swift
@Environment(\.colorSchemeMode) var colorSchemeMode
// ...
MyView()
.colorSchemeMode($colorSchemeMode)
```

For a `ColorSchemeMode` that can be also persisted even after the app is closed try using `@AppStorage`:
```swift
@AppStorage("colorSchemeMode") var colorSchemeMode

MyView()
.colorSchemeMode($colorSchemeMode)
```


## Known Limitations

We can only apply night mode to views within the SwiftUI View hierarchy. This does not include system views such as the status bar at the top of the screen.
Expand All @@ -111,12 +131,12 @@ Be sure to test your design in all use cases. Some things to look out for:

- Night Mode will of course filter out blue light (that's the whole point of it). For this reason, blue elements can become invisible or difficult to see.
- Since Night Mode is monochrome, your UI cannot use color to communicate to the user. Therefore, it's recommended to:
- Add the SwiftUI enironment value [accessibilityDifferentiateWithoutColor](https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilitydifferentiatewithoutcolor) to tell all child views when they need to use shapes, rather than colors to communicate to the user. (In an upcoming release PlusNightMode will handle this automatically.)
- Add the SwiftUI environment value [accessibilityDifferentiateWithoutColor](https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilitydifferentiatewithoutcolor) to tell all child views when they need to use shapes, rather than colors to communicate to the user. (In an upcoming release PlusNightMode will handle this automatically.)
- Add simple logic for child views to respect accessibilityDifferentiateWithoutColor. *Hacking with Swift* has a very helpful [tutorial](https://www.hackingwithswift.com/books/ios-swiftui/supporting-specific-accessibility-needs-with-swiftui) on this subject.

## Collaboration

Please feel free to open a PR.

## Thank Yous
- [Swift Package Index](https://swiftpackageindex.com/) for mentioning us in their [podcast episode](https://podcasts.apple.com/us/podcast/39-stress-testing-dependency-management/id1654567329?i=1000641328907) (at 26:00).
- [Swift Package Index](https://swiftpackageindex.com/) for mentioning us in their [podcast episode](https://podcasts.apple.com/us/podcast/39-stress-testing-dependency-management/id1654567329?i=1000641328907) (at 26:00).
13 changes: 7 additions & 6 deletions Sources/PlusNightMode/ColorSchemeMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import Foundation
import SwiftUI

/// A wrapper type that provides extra functionality to SwiftUI's `ColorScheme`
///
/// To get the underlying `ColorScheme` value, use `resolvedColorScheme`
public struct ColorSchemeMode: Sendable {
public var value: Value

Expand All @@ -20,18 +23,16 @@ public struct ColorSchemeMode: Sendable {
self.value = value
}

/// A wrapper enum that provides extra functionality to SwiftUI's `ColorScheme`
///
/// To get the underlying `ColorScheme` value, use `resolvedColorScheme`
/// The underlying value of the ``ColorSchemeMode``.
public enum Value: String, CaseIterable, Hashable, Codable, Identifiable, Sendable {
/// Light Mode
case light = "Light"
/// Dark Mode
case dark = "Dark"
/// A View presentation designed to minimize negative impact to sleep where every
/// pixel is either pitch black or a shade of red.
/// A View presentation designed to minimize the negative impact of harmful sleep-depriving blue light.
/// This appearance effectively makes it so that every pixel is either pitch black or a shade of red.
/// Note: Night Mode is not built into the system. Instead, you can observe the `\.colorSchemeMode`
/// EnvironmentKey and respond to it using `observingNightMode()`
/// EnvironmentKey and respond to it using `observingNightMode()` or `colorSchemeMode(_:)`
case night = "Night"
/// In this mode, the App will simply observe the Dark/Light mode setting of the device.
case auto = "Automatic"
Expand Down
86 changes: 86 additions & 0 deletions Sources/PlusNightMode/Documentation.docc/Getting Started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Getting Started


``PlusNightMode`` makes it easy to add dynamic, user-configurable appearance controls to any SwiftUI View.


``PlusNightMode`` supports 4 appearance settings:
1. ``ColorSchemeMode.Value.light``: A light mode appearance
2. ``ColorSchemeMode.Value.dark``: A dark mode appearance
3. ``ColorSchemeMode.Value.auto``: An appearance that will automatically switch between light and dark appearance according to the parent View's color scheme. (If every ancestor View has a color scheme that has not been overridden, then this appearance will follow the appearance of the device.)
4. ``ColorSchemeMode.Value.night``: A night mode appearance

## What's Night Mode
Night Mode is an appearance designed to aggressively reduce the amount of harmful, sleep-depriving blue light. It does this by a combination of these strategies:
1. Under the hood, the night mode appearance will give every child SwiftUI View a dark SwiftUI color scheme.
2. Then the Night Mode appearance will apply a red color multiply filter over the View.

This effectively means that every pixel of View will be pitch black or a shade of red!

## `ColorScheme` vs. `ColorSchemeMode`
- SwiftUI has a type called [ColorScheme](https://developer.apple.com/documentation/swiftui/colorscheme):
- This only has support for light and dark mode.
- There's no support for auto or night.
- PlusNightMode introduces a new type called ``ColorSchemeMode``:
- It adds support for auto and night appearance.

## Adding Night Mode
The easiest way to add night mode is to use the ``nightModed(if:)`` method and pass it a value of `true`. This method will force the View (and all its children to have a night mode appearance). (However, it will pay no attention to users settings in the app, nor in device settings.)

```swift
@State var shouldNightMode = true
// ...
MyView()
.nightModed(if: shouldNightMode)
```

But the best way to add night mode to your View is to use ``colorSchemeMode(_:)``. This will ensure that the SwiftUI View (and all its children) will dynamically update to match the user's current settings both in the app and in the device settings.

```swift
@Environment(\.colorSchemeMode) var colorSchemeMode
// ...
MyView()
.colorSchemeMode($colorSchemeMode)
```
## Adding User Configurability
Now you can add the color scheme mode to your app's settings like this, and the changes will be visible across the app!

```swift
// In your Settings Screen
@Environment(\.colorSchemeMode) var colorSchemeMode
// ...
Picker("ColorSchemeMode", selection: $colorSchemeMode) {
Text("Night").tag(ColorSchemeMode.night)
Text("Dark").tag(ColorSchemeMode.dark)
Text("Light").tag(ColorSchemeMode.light)
Text("Auto").tag(ColorSchemeMode.auto)
}
.pickerStyle(.menu)
```

## On View Hierarchy
It is important to note that ``colorSchemeMode(_:)`` can only apply the night mode appearance to children views. It cannot apply the night mode appearance to parent or sibling views. Because of this, it is best practice to:
1. Apply ``colorSchemeMode(_:)`` at the highest possible level of each View hierarchy. The higher the better, since only children will be affected.
2. Reapply ``colorSchemeMode(_:)`` when creating a branching view hierarchy:
- Certain views such as view presentations, like SwiftUI's `.sheet` create an entirely new View hierarchy. For this you need to reapply ``colorSchemeMode(_:)`` to the presented View.

```swift
struct ExampleView: View {
@Environment(\.colorSchemeMode) var colorSchemeMode

var body: some View {
Button {
isPresenting.toggle()
} label: {
Text("Present Sheet View")
}
.colorSchemeMode($colorSchemeMode)
.sheet(isPresented: $isPresenting) {
Text("Presented View")
.colorSchemeMode($colorSchemeMode)
// 👆🏼 SwiftUI will not add Night Mode to
// presented views unless you explicitly add it
}
}
}
```
2 changes: 1 addition & 1 deletion Sources/PlusNightMode/ExampleNightModeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
/// An example `View` to demonstrate how ``colorSchemeMode(_:)`` affects appearance.
///
/// Also used in tests.
@available(iOS 16.0, macOS 13.0, *)
@available(iOS 16.0, macOS 14.0, *)
public struct ExampleNightModeView: View {
public init(colorSchemeMode: ColorSchemeMode = .night) {
self._colorSchemeMode = State(initialValue: colorSchemeMode)
Expand Down
2 changes: 1 addition & 1 deletion Sources/PlusNightMode/View + Night Mode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension View {
}

@ViewBuilder
public func observingNightMode(_ colorSchemeMode: Binding<ColorSchemeMode>) -> some View {
func observingNightMode(_ colorSchemeMode: Binding<ColorSchemeMode>) -> some View {
let bool = colorSchemeMode.wrappedValue == .night


Expand Down

0 comments on commit fd51b0d

Please sign in to comment.