更新:首先,该项目需要支持iOS 14。
我正在开发一个混合项目(SwiftUI + UIKit),所以我必须使用 UINavigationController 作为窗口的根控制器。 (或者我无法推送任何 UIKitViewController)
根源是这样的:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else {
return
}
let window = UIWindow(windowScene: windowScene)
window.makeKeyAndVisible()
window.overrideUserInterfaceStyle = .light
self.window = window
//UIKit Navigation as root
let rootView = AnyView(SwiftUIView1())
let rootVC = UIHostingController(rootView: rootView)
let rootNavC = UINavigationController(rootViewController: rootHubVC)
window.rootViewController = rootNavC
}
在 SwiftUIView1 内部,它有一个 StateObject
class TestingState: ObservableObject, Equatable {
static func == (lhs: TestingState, rhs: TestingState) -> Bool {
lhs.currentJourneyLevel == lhs.currentJourneyLevel
}
@Published var currentJourneyLevel: Int {
willSet {
debugPrint("##### currentJourneyLevel will be set from \(currentJourneyLevel) to \(newValue)")
}
}
init() {
currentJourneyLevel = 1
}
}
struct SwiftUIView1: View {
@StateObject private var testingState = TestingState()
@State var isActived = false {
didSet {
debugPrint("##### isActived: \(isActived)")
}
}
var body: some View {
VStack (spacing: 30) {
Button("NextPage") {
testingState.currentJourneyLevel = 2
}
NavigationLink(
destination: SwiftUIView1(state: testingState),
isActive: $isActived
) { EmptyView() }
}
.onChange(of: testingState.currentJourneyLevel) { currentJourneyLevel in
debugPrint("##### currentJourneyLevel is onChange to: \(currentJourneyLevel)")
isActived = currentJourneyLevel == 2
}
.navigationTitle("SwiftUIView1")
.navigationBarTitleDisplayMode(.inline)
}
}
在 SwiftUIView2 内部,它有另一个按钮可以将 currentJourneyLevel 设置回 1,以将其弹出到第一个视图,如下所示:
struct SwiftUIView2: View {
private var state: TestingState
init(state: TestingState) {
self.state = state
}
var body: some View {
ZStack {
VStack(spacing: 10) {
Button("Back") {
state.currentJourneyLevel = 1
}
}
.navigationTitle("SwiftUIView2")
}
}
}
一旦我单击 NextPage 按钮,就按预期工作,currentJourneyLevel 更改为 2,然后调用 onChange 块,最后 isActived 将设置为 true,这是日志:
"##### currentJourneyLevel will be set from 1 to 2"
"##### currentJourneyLevel is onChange to: 2"
"##### isActived: true"
问题是: 当我尝试从第二个视图返回时,系统未按预期调用 onChange 块。
从日志中我可以看到 currentJourneyLevel 的值已正确更改,但似乎系统观察器不起作用,它没有调用应有的 onChange 方法。
"##### currentJourneyLevel will be set from 2 to 1"
但是,如果我更改为纯 SwiftUI 导航,如下所示:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else {
return
}
let window = UIWindow(windowScene: windowScene)
window.makeKeyAndVisible()
window.overrideUserInterfaceStyle = .light
self.window = window
//Pure SwiftUI Navigation
let rootView = SwiftUIContainerView()
let rootVC = UIHostingController(rootView: rootView)
window.rootViewController = rootVC
//UIKit Navigation as root
// let rootView = AnyView(SwiftUIView1())
// let rootVC = UIHostingController(rootView: rootView)
// let rootNavC = UINavigationController(rootViewController: rootVC)
// window.rootViewController = rootNavC
}
struct SwiftUIContainerView: View {
var body: some View {
NavigationView {
AnyView(SwiftUIView1())
}
}
}
一切都会顺利进行。单击下一页按钮:
"##### currentJourneyLevel will be set from 1 to 2"
"##### currentJourneyLevel is onChange to: 2"
"##### isActived: true"
单击返回按钮:
"##### currentJourneyLevel will be set from 2 to 1"
"##### currentJourneyLevel is onChange to: 1"
"##### isActived: false"
所以, 我应该怎么做才能使 onChange 块与 UIKitNavigationController 一起工作? 或者如果我犯了任何错误?
任何建议将不胜感激
将视图/控制器推送到 NavigationStack/UINavigationController 上时,它们覆盖的视图/控制器会在动画完成后从视图层次结构中删除(onDisappear/viewDidDisappear)。
如果您从堆栈内部使用
.onChange()
,则会产生奇怪的效果,因为仅当视图/控制器在屏幕上时才会调用闭包。我已经遇到过几次这种情况,需要将我的观察逻辑提取到一个仍然可见的地方,最好是在导航视图/控制器之外。
在我看来,你的
SwiftUIContainerView
最有潜力。
struct SwiftUIContainerView: View {
let testingState: TestingState // might need to be a '@State var'
var body: some View {
NavigationStack {
SwiftUIView1(state: testingState)
}
.onChange(testingState.currentJourneyLevel) { level in
/// should be called regardless of the visibility of SwiftUIView1
}
}
}
另一个需要考虑的途径是 navigationDestination(for:) 修饰符 与 NavigationStack 上的路径参数协调。
enum Destinations: Hashable {
case view2
}
如果您使用
Destinations
数组作为路径,则可以更轻松地观察哪些视图已被推入堆栈。
@State private var path = [Destinations]()
var body: some View {
NavigationStack(path: $path) {
SwiftUIView1(state: testingState)
.navigationDestination(for: Destinations.self) { destination in
SwiftUIView2()
}
}
.onChange(path.last == .view2) { isActive in
/// called when a SwiftUIView2 is pushed/popped
}
}
nb:NavigationView 已被弃用,取而代之的是 NavigationStack。
(此外,所有代码都在浏览器中输入,未经编译器检查)