如何解决SwiftUI:如何让用户使用“亮”、“暗”和“系统”选项实时设置应用程序外观?
我目前正在尝试在应用中实施一个解决方案,让用户能够使用以下选项实时切换应用的外观:
- 系统(应用在设备的 iOS 设置中设置的任何外观)
- 浅色(应用 .light 配色方案)
- 深色(应用 . 深色配色方案)
事实证明,使用 .preferredColorScheme() 设置浅色和深色配色方案非常容易且响应迅速;但是,对于“系统”选项,我还没有找到任何令人满意的解决方案。
我目前的方法如下:
- 在 ContentView 中使用 @Environment(.colorScheme) 获取设备配色方案
- 创建自定义视图修饰符以在任何视图上应用相应的配色方案
- 使用“MainView”上的修饰符(即应用的真实内容应该存在的地方)在配色方案之间切换
我的想法是在 ContentView 中嵌入 MainView,这样@Environment(.colorScheme) 就不会被任何应用于 MainView 的 colorScheme 所干扰。
但是,它仍然没有按预期工作:设置明暗外观时,一切都按预期工作。但是,当从亮/暗切换到“系统”时,外观的变化只有在重新启动应用程序后才能看到。然而,预期的行为是外观立即改变。
对此有什么想法吗?
以下是相关代码片段:
主视图
import SwiftUI
struct MainView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
var body: some View {
VStack {
Spacer()
Button(action: {
selectedAppearance = 1
}) {
Text("Light")
}
Spacer()
Button(action: {
selectedAppearance = 2
}) {
Text("Dark")
}
Spacer()
Button(action: {
selectedAppearance = 0
}) {
Text("System")
}
Spacer()
}
}
}
内容视图
import SwiftUI
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
var body: some View {
MainView()
.modifier(ColorSchemeModifier(colorScheme: colorScheme))
}
}
“实用程序”
import Foundation
import SwiftUI
struct ColorSchemeModifier: ViewModifier {
@AppStorage("selectedAppearance") var selectedAppearance: Int = 0
var colorScheme: ColorScheme
func body(content: Content) -> some View {
if selectedAppearance == 2 {
return content.preferredColorScheme(.dark)
} else if selectedAppearance == 1 {
return content.preferredColorScheme(.light)
} else {
return content.preferredColorScheme(colorScheme)
}
}
}
解决方法
我最终使用了以下解决方案,这是对@pgb 给出的答案的轻微改编:
内容视图:
#include <Windows.h>
#include <iostream>
HHOOK hMouseHook;
LRESULT CALLBACK mouseProc(int nCode,WPARAM wParam,LPARAM lParam)
{
MOUSEHOOKSTRUCT* pMouseStruct = (MOUSEHOOKSTRUCT*)lParam;
if (pMouseStruct != NULL)
printf("X: %d Y: %d\n",pMouseStruct->pt.x,pMouseStruct->pt.y);
return CallNextHookEx(hMouseHook,nCode,wParam,lParam);
}
int main() {
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL,mouseProc,0);
MSG message;
while (GetMessage(&message,NULL,0)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
UnhookWindowsHookEx(hMouseHook);
return 0;
}
助手类
struct ContentView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
var utilities = Utilities()
var body: some View {
VStack {
Spacer()
Button(action: {
selectedAppearance = 1
}) {
Text("Light")
}
Spacer()
Button(action: {
selectedAppearance = 2
}) {
Text("Dark")
}
Spacer()
Button(action: {
selectedAppearance = 0
}) {
Text("System")
}
Spacer()
}
.onChange(of: selectedAppearance,perform: { value in
utilities.overrideDisplayMode()
})
}
}
,
我认为这在 SwiftUI 中是不可能的。
从文档来看,使用 preferredColorScheme
将影响整个层次结构,从封闭的演示文稿开始:
配色方案适用于最近的封闭演示文稿,例如弹出窗口或窗口。视图可以使用 colorScheme 环境值读取配色方案。
因此,这会影响您的整个视图层次结构,这意味着您不能仅针对某些视图覆盖它。相反,它会“冒泡”并更改整个窗口(或演示文稿上下文)的配色方案。
然而,您可以从 UIKit
更改它,但使用 overrideUserInterfaceStyle
,它也是一个支持 .dark
、.light
和 {{1} }.未指定为系统设置。
这对您的应用有何影响?好吧,我认为您根本不需要视图修饰符。相反,您需要监视 .unspecified
以了解 UserDefaults
键的更改,并通过将 selectedAppearance
更改为适当的值来对其做出反应。
因此,取决于您的应用程序其余部分的结构,在您的 overrideUserInterfaceStyle
或 AppDelegate
中(您也可以使用任何其他对象,但它需要访问呈现的 {{ 1}},因此在重构时请记住这一点),您可以将侦听器连接到 SceneDelegate
以侦听更改。像这样:
UIWindow
然后覆盖 UserDefaults
以监控对默认键的更改:
UserDefaults.standard.addObserver(self,forKeyPath: "selectedAppearance",options: [.new],context: nil)
还有一些需要改进的地方(这个问题没有上下文,但我认为值得一提):
- 对于外观的有效值,我会使用
observeValue
而不是override func observeValue(forKeyPath keyPath: String?,of object: Any?,change: [NSKeyValueChangeKey : Any]?,context: UnsafeMutableRawPointer?) { guard let userDefaults = object as? UserDefaults,keyPath == "selectedAppearance" else { return } switch userDefaults.integer(forKey: "selectedAppearance") { case 2: window?.overrideUserInterfaceStyle = .dark case 1: window?.overrideUserInterfaceStyle = .light default: window?.overrideUserInterfaceStyle = .unspecified } }
。 - 假设
enum
提供了一个只包含您想要的 3 个值的枚举,我会重用它,因此您不需要integer
调用中的UIKit
。
适用于
.preferredColorScheme(selectedAppearance == 1 ? .light : selectedAppearance == 2 ? .dark : nil)
iOS 14.5+: 这就对了。他们让“nil”工作来重置您的首选颜色方案。
iOS14:
唯一的问题是您必须重新加载应用程序才能让系统正常工作。我可以想到一个解决方法 - 当用户选择“系统”时,您首先确定当前的 colorScheme 是什么,将其更改为它,然后才将 selectedAppearance 更改为 0。
- 用户会立即看到结果,下次启动时它将成为系统主题。
编辑:
这是工作的想法:
struct MainView: View {
@Binding var colorScheme: ColorScheme
@AppStorage("selectedAppearance") var selectedAppearance = 0
var body: some View {
VStack {
Spacer()
Button(action: {
selectedAppearance = 1
}) {
Text("Light")
}
Spacer()
Button(action: {
selectedAppearance = 2
}) {
Text("Dark")
}
Spacer()
Button(action: {
if colorScheme == .light {
selectedAppearance = 1
}
else {
selectedAppearance = 2
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
selectedAppearance = 0
}
}) {
Text("System")
}
Spacer()
}
}
}
struct ContentView: View {
@AppStorage("selectedAppearance") var selectedAppearance = 0
@Environment(\.colorScheme) var colorScheme
@State var onAppearColorScheme: ColorScheme = .light //only for iOS<14.5
var body: some View {
MainView(colorScheme: $onAppearColorScheme)
.onAppear { onAppearColorScheme = colorScheme } //only for iOS<14.5
.preferredColorScheme(selectedAppearance == 1 ? .light : selectedAppearance == 2 ? .dark : nil) }
}
有一个小问题 -> 它仅适用于 onAppear 而不是 onChange(不知道为什么)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。