如何创建一个 NavigationManager 来更改视图模型中的选项卡?
How to create a NavigationManager that would change tab within view models?
我有一个 NavigationManager 来处理更改 SwiftUI 选项卡栏选择。
如果在我的 SwiftUI 视图中将其设置为 @EnvironmentObject
,它会起作用,但在我的视图模型中将 NavigationManager 作为服务调用时则不起作用。问题是我想使用一个更简单的解决方案,而不是四处传递 @EnvironmentObject var navigationManager
并将它们传递到视图模型初始化程序中,因为我有很多,我正在寻找一种更简洁的方法。
如何使用我的 NavigationManager 从我的视图模型中更改选项卡而不在 init() 中传递它?
import SwiftUI
struct ContentView: View {
@StateObject var navigationManager = NavigationManager()
var body: some View {
TabView(selection: $navigationManager.selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
.environmentObject(navigationManager)
}
}
}
我想在视图模型中使用的导航管理器。
class NavigationManager: ObservableObject {
@Published var selection: NavigationItem = .account
}
enum NavigationItem {
case account
case settings
}
我的 AccountViewModel 和设置视图模型:
class AccountViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct AccountView: View {
@StateObject var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection = .settings
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct SettingsView: View {
@EnvironmentObject var navigationManager: NavigationManager
@StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
navigationManager.selection = .account
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
我成功地使用 属性 包装器将我的 navigationManager 作为共享依赖项注入,并使用 Combine 更改其 selection
变量。
所以我创建了一个协议,将 NavigationManager
包装到 属性 包装器 @Injection 并使其值成为 CurrentValueSubject
import Combine
final class NavigationManager: NavigationManagerProtocol, ObservableObject {
var selection = CurrentValueSubject<NavigationItem, Never>(.settings)
}
protocol NavigationManagerProtocol {
var selection: CurrentValueSubject<NavigationItem, Never> { get set }
}
这是 @Injection 属性 包装器,用于在 .swift 个文件之间传递我的 NavigationManager
实例。
@propertyWrapper
struct Injection<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
extension InjectedDependency {
var navigationManager: NavigationManagerProtocol {
get { Self[NavigationManagerKey.self] }
set { Self[NavigationManagerKey.self] = newValue }
}
}
private struct NavigationManagerKey: InjectedKeyProtocol {
static var currentValue: NavigationManagerProtocol = NavigationManager()
}
有了这个,我可以在我的视图模型之间传递我的 NavigationManager
并在点击按钮时使用 Combine 发送新值:
class AccountViewModel: ObservableObject {
@Injection(\.navigationManager) var navigationManager
}
struct AccountView: View {
var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.settings)
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
@Injection(\.navigationManager) var navigationManager
}
struct SettingsView: View {
@StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.account)
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
最后,我在我的 ContentView 中注入 NavigationManager
并使用 .onReceive(_:action:)
修饰符从代码中的任何位置跟踪新选择的选项卡。
struct ContentView: View {
@Injection(\.navigationManager) var navigationManager
@State var selection: NavigationItem = .account
var body: some View {
TabView(selection: $selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
}
.onReceive(navigationManager.selection) { newValue in
selection = newValue
}
}
}
我有一个 NavigationManager 来处理更改 SwiftUI 选项卡栏选择。
如果在我的 SwiftUI 视图中将其设置为 @EnvironmentObject
,它会起作用,但在我的视图模型中将 NavigationManager 作为服务调用时则不起作用。问题是我想使用一个更简单的解决方案,而不是四处传递 @EnvironmentObject var navigationManager
并将它们传递到视图模型初始化程序中,因为我有很多,我正在寻找一种更简洁的方法。
如何使用我的 NavigationManager 从我的视图模型中更改选项卡而不在 init() 中传递它?
import SwiftUI
struct ContentView: View {
@StateObject var navigationManager = NavigationManager()
var body: some View {
TabView(selection: $navigationManager.selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
.environmentObject(navigationManager)
}
}
}
我想在视图模型中使用的导航管理器。
class NavigationManager: ObservableObject {
@Published var selection: NavigationItem = .account
}
enum NavigationItem {
case account
case settings
}
我的 AccountViewModel 和设置视图模型:
class AccountViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct AccountView: View {
@StateObject var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection = .settings
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
let navigationManager = NavigationManager()
}
struct SettingsView: View {
@EnvironmentObject var navigationManager: NavigationManager
@StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
navigationManager.selection = .account
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
我成功地使用 属性 包装器将我的 navigationManager 作为共享依赖项注入,并使用 Combine 更改其 selection
变量。
所以我创建了一个协议,将 NavigationManager
包装到 属性 包装器 @Injection 并使其值成为 CurrentValueSubject
import Combine
final class NavigationManager: NavigationManagerProtocol, ObservableObject {
var selection = CurrentValueSubject<NavigationItem, Never>(.settings)
}
protocol NavigationManagerProtocol {
var selection: CurrentValueSubject<NavigationItem, Never> { get set }
}
这是 @Injection 属性 包装器,用于在 .swift 个文件之间传递我的 NavigationManager
实例。
@propertyWrapper
struct Injection<T> {
private let keyPath: WritableKeyPath<InjectedDependency, T>
var wrappedValue: T {
get { InjectedDependency[keyPath] }
set { InjectedDependency[keyPath] = newValue }
}
init(_ keyPath: WritableKeyPath<InjectedDependency, T>) {
self.keyPath = keyPath
}
}
protocol InjectedKeyProtocol {
associatedtype Value
static var currentValue: Self.Value { get set }
}
struct InjectedDependency {
private static var current = InjectedDependency()
static subscript<K>(key: K.Type) -> K.Value where K: InjectedKeyProtocol {
get { key.currentValue }
set { key.currentValue = newValue }
}
static subscript<T>(_ keyPath: WritableKeyPath<InjectedDependency, T>) -> T {
get { current[keyPath: keyPath] }
set { current[keyPath: keyPath] = newValue }
}
}
extension InjectedDependency {
var navigationManager: NavigationManagerProtocol {
get { Self[NavigationManagerKey.self] }
set { Self[NavigationManagerKey.self] = newValue }
}
}
private struct NavigationManagerKey: InjectedKeyProtocol {
static var currentValue: NavigationManagerProtocol = NavigationManager()
}
有了这个,我可以在我的视图模型之间传递我的 NavigationManager
并在点击按钮时使用 Combine 发送新值:
class AccountViewModel: ObservableObject {
@Injection(\.navigationManager) var navigationManager
}
struct AccountView: View {
var viewModel = AccountViewModel()
var body: some View {
VStack(spacing: 16) {
Text("AccountView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.settings)
}) {
Text("Go to Settings tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
class SettingsViewModel: ObservableObject {
@Injection(\.navigationManager) var navigationManager
}
struct SettingsView: View {
@StateObject var viewModel = SettingsViewModel()
var body: some View {
VStack(spacing: 16) {
Text("SettingsView")
.font(.title3)
Button(action: {
viewModel.navigationManager.selection.send(.account)
}) {
Text("Go to Account tab")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
}
}
最后,我在我的 ContentView 中注入 NavigationManager
并使用 .onReceive(_:action:)
修饰符从代码中的任何位置跟踪新选择的选项卡。
struct ContentView: View {
@Injection(\.navigationManager) var navigationManager
@State var selection: NavigationItem = .account
var body: some View {
TabView(selection: $selection) {
AccountView()
.tabItem {
Text("Account")
Image(systemName: "person.crop.circle") }
.tag(NavigationItem.account)
SettingsView()
.tabItem {
Text("Settings")
Image(systemName: "gear") }
.tag(NavigationItem.settings)
}
.onReceive(navigationManager.selection) { newValue in
selection = newValue
}
}
}