You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI嵌套NavigationLink交互异常:操作控件自动返回页面求助

问题描述

开发Scholar Hours App时遇到嵌套NavigationLink问题:进入Dashboard/Settings页面后,切换Toggle或操作Stepper,应用会自动返回Dashboard页面,调试控制台出现约束错误,最终程序因signal 9终止。

相关代码

//
//  ContentView.swift
//  Scholar Hours App
//
//  Created by Evan Scharnhorst on 8/13/22.
//

import SwiftUI

extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Element].self, from: data)
        else {
            return nil
        }
        self = result
    }

    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let result = String(data: data, encoding: .utf8)
        else {
            return "[]"
        }
        return result
    }
}

struct ContentView: View {
        @State var hours = 7
    @State var minutes: Double = 0
    @AppStorage("log") var log: [String] = []
    var time = Date.now
    let formatter1 = DateFormatter()
    var isStripe = 0
    @AppStorage("hoursGoal") var hoursGoal = 0
    @AppStorage("minutesGoal") var minutesGoal = 0
    @AppStorage("practiceScholar") var practiceScholar = true
    @AppStorage("weeklyScholarDays") var weeklyScholarDays = 3
    
    func incrementStep() {
       minutesGoal += 30
       if (minutesGoal == 60) {
           hoursGoal += 1
           minutesGoal = 0
       }
    }

    func decrementStep() {
       minutesGoal -= 30
       if (minutesGoal == -30) {
           hoursGoal -= 1
           minutesGoal = 30
       }
    }
    
    
        var body: some View {
            NavigationView {
                VStack{
                    NavigationLink {
                        List{
                            Section {
                                    Stepper("\(hours) hours", value: $hours, in: 3...12)
                            }header: {
                                    Text("Scholar Hours")
                            }
                                Section{
                                Slider(value: $minutes,in: 0...59, step: 1)
                                Text("\(Int(minutes)) minutes")
                            } header: {
                                Text("Scholar Minutes")
                            }
                            
                            Section{
                                Button("Log Hours") {
                                    formatter1.dateStyle = .short
                                    log.insert(formatter1.string(from: time), at: 0)
                                    log.insert("\(hours)h \(Int(minutes))m", at: 0)
                                    print(log)
                                }
                            }footer: {
                                Text("Your log will be editable. To edit, please go to the \"\"Log\"\" page.")
                            }
                        }
                    .navigationTitle("Scholar")
                    }label: {
                        Text("Scholar")
                            .font(.title)
                    }
                    
                    NavigationLink(destination: Text("Second View")) {
                        Text("Love of Learning")
                            .font(.title)
                    }
                    NavigationLink(destination: Text("Second View")) {
                        Text("Break")
                            .font(.title)
                    }
                    NavigationLink {
                        List {
                            Section{
                                Text("5h 32m")
                                    .font(.system(size: 45, weight: .bold, design: .default))
                                    .frame(maxWidth: .infinity, alignment: .center)
                                    .padding(20)
                                NavigationLink{
                                     Text("Blah, Blah, Blah")
                                        .navigationTitle("More Stats")
                                }label: {
                                    Text("More Stats")
                                }
                            }header: {
                                Text("Extra Hours")
                                    .padding(0)
                                    
                            }
                            
                                
                        
                            Section {
                                
                                
                                
                                NavigationLink {
                                    List {
                                        Section{
                                            Stepper {
                                                Text("\(hoursGoal)h \(minutesGoal)m")
                                            } onIncrement: {
                                               
                                                minutesGoal += 30
                                                if (minutesGoal == 60) {
                                                    hoursGoal += 1
                                                    minutesGoal = 0
                                                }
                                                
                                            } onDecrement: {
                                                minutesGoal -= 30
                                                if (minutesGoal == -30) {
                                                    hoursGoal -= 1
                                                    minutesGoal = 30
                                                }
                                            }
                                        } header: {
                                            Text("Daily Goal")
                                        }footer: {
                                            Text("How many scholar hours per scholar day.")
                                        }
                                        Section {
                                            Toggle("Practice Scholar", isOn: $practiceScholar)
                                        }footer: {
                                            Text("Practice scholar means that you will a some love of learning days in your week")
                                        }
                                        if(practiceScholar == true){
                                            Section {
                                                Stepper("\(weeklyScholarDays) days", value: $weeklyScholarDays, in: 1...4)
                                            }header: {
                                                Text("Weekly Scholar Days")
                                            }footer: {
                                                Text("How many scholar days per week.")
                                            }
                                        }
                                    }
                                    .navigationTitle("Settings")
                                }label: {
                                    Text("Settings")
                            }
                                
                                
                                
                                
                                
                                
//                                NavigationLink {
//                                    if(log != []){
//                                        List(log, id: \.self) { i in
//                                            HStack {
//                                                if((Int(i) ?? 0) % 2 == 0) {
//                                                 Text(i)
//                                                 Spacer()
//                                                 Text(log[(Int(i) ?? 0) + 1])
//                                                }
//                                            }
//                                            .navigationTitle("Log")
//
//                                        }
//                                        .listStyle(GroupedListStyle())
//                                    }else{
//                                        Text("Uh oh, there are no logs, you can log your study time in \"\"Scholar\"\" or \"\"Love of Learning\"\".")
//                                            .padding(30)
//                                            .navigationTitle("Log")
//                                    }
//
//
//                                }label: {
//                                    Label("Log", systemImage: "clock.fill")
//                                        .foregroundColor(.white)
//                                }
                                
                                NavigationLink {
                                    Text("book log view")
                                        .navigationTitle("Book Log")
                                }label: {
                                    Label("Book Log", systemImage: "book.closed.fill")
                                        .foregroundColor(.white)
                                }
                            }
                        }
                        .navigationTitle("Dashboard")
                        }label: {
                            Text("Dashboard")
                                .font(.title)
                        }
                }
                
                .buttonStyle(.bordered)
                .padding()
                .navigationBarTitle("Log Hours")
            }
        }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

调试控制台错误信息

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000004fbf20 UIView:0x7fbc937277d0.trailing == _UIBackButtonMaskView:0x7fbc93727650.trailing (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
Message from debugger: Terminated due to signal 9


解决建议

问题根源

所有页面逻辑都堆在ContentView里,当@AppStorage修饰的全局状态(比如practiceScholar、hoursGoal)变化时,整个主视图会重新渲染,嵌套的NavigationLink被强制重建,导致意外返回上一页。同时频繁的视图重建引发UIKit导航栏约束冲突,最终触发系统强制终止(signal 9)。

具体修复步骤

  1. 拆分页面为独立View结构体
    把Scholar、Dashboard、Settings页面拆成单独的View,状态变化只会影响对应子View,不会触发主视图重渲染。

  2. 优化状态管理
    将仅在单个页面使用的状态(比如hours、minutes)移到对应子View内部,减少全局状态的影响范围。

  3. 复用逻辑代码
    Settings页面的Stepper逻辑和主视图重复,直接调用已有的incrementStep、decrementStep方法,避免冗余计算。

  4. 简化视图嵌套
    清理List Section中的过度嵌套,减少布局冲突概率。

修改后的示例代码

主ContentView

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack{
                NavigationLink {
                    ScholarView()
                } label: {
                    Text("Scholar")
                        .font(.title)
                }
                
                NavigationLink(destination: Text("Second View")) {
                    Text("Love of Learning")
                        .font(.title)
                }
                NavigationLink(destination: Text("Second View")) {
                    Text("Break")
                        .font(.title)
                }
                NavigationLink {
                    DashboardView()
                } label: {
                    Text("Dashboard")
                        .font(.title)
                }
            }
            .buttonStyle(.bordered)
            .padding()
            .navigationBarTitle("Log Hours")
        }
    }
}

ScholarView

struct ScholarView: View {
    @State var hours = 7
    @State var minutes: Double = 0
    @AppStorage("log") var log: [String] = []
    let time = Date.now
    let formatter1 = DateFormatter()
    
    var body: some View {
        List{
            Section {
                Stepper("\(hours) hours", value: $hours, in: 3...12)
            } header: {
                Text("Scholar Hours")
            }
            Section{
                Slider(value: $minutes,in: 0...59, step: 1)
                Text("\(Int(minutes)) minutes")
            } header: {
                Text("Scholar Minutes")
            }
            
            Section{
                Button("Log Hours") {
                    formatter1.dateStyle = .short
                    log.insert(formatter1.string(from: time), at: 0)
                    log.insert("\(hours)h \(Int(minutes))m", at: 0)
                    print(log)
                }
            } footer: {
                Text("Your log will be editable. To edit, please go to the \"Log\" page.")
            }
        }
        .navigationTitle("Scholar")
    }
}

DashboardView

struct DashboardView: View {
    var body: some View {
        List {
            Section{
                Text("5h 32m")
                    .font(.system(size: 45, weight: .bold, design: .default))
                    .frame(maxWidth: .infinity, alignment: .center)
                    .padding(20)
                NavigationLink{
                    Text("Blah, Blah, Blah")
                        .navigationTitle("More Stats")
                } label: {
                    Text("More Stats")
                }
            } header: {
                Text("Extra Hours")
                    .padding(0)
            }
            
            Section {
                NavigationLink {
                    SettingsView()
                } label: {
                    Text("Settings")
                }
                
                NavigationLink {
                    Text("book log view")
                        .navigationTitle("Book Log")
                } label: {
                    Label("Book Log", systemImage: "book.closed.fill")
                        .foregroundColor(.white)
                }
            }
        }
        .navigationTitle("Dashboard")
    }
}

SettingsView

struct SettingsView: View {
    @AppStorage("hoursGoal") var hoursGoal = 0
    @AppStorage("minutesGoal") var minutesGoal = 0
    @AppStorage("practiceScholar") var practiceScholar = true
    @AppStorage("weeklyScholarDays") var weeklyScholarDays = 3
    
    func incrementStep() {
        minutesGoal += 30
        if (minutesGoal == 60) {
            hoursGoal += 1
            minutesGoal = 0
        }
    }
    
    func decrementStep() {
        minutesGoal -= 30
        if (minutesGoal == -30) {
            hoursGoal -= 1
            minutesGoal = 30
        }
    }
    
    var body: some View {
        List {
            Section{
                Stepper {
                    Text("\(hoursGoal)h \(minutesGoal)m")
                } onIncrement: {
                    incrementStep()
                } onDecrement: {
                    decrementStep()
                }
            } header: {
                Text("Daily Goal")
            } footer: {
                Text("How many scholar hours per scholar day.")
            }
            Section {
                Toggle("Practice Scholar", isOn: $practiceScholar)
            } footer: {
                Text("Practice scholar means that you will have some love of learning days in your week")
            }
            if(practiceScholar == true){
                Section {
                    Stepper("\(weeklyScholarDays) days", value: $weeklyScholarDays, in: 1...4)
                } header: {
                    Text("Weekly Scholar Days")
                } footer: {
                    Text("How many scholar days per week.")
                }
            }
        }
        .navigationTitle("Settings")
    }
}

修复效果

拆分视图后,状态变化仅影响对应子页面,不会重建主视图的NavigationLink,解决了自动返回的问题;同时减少了视图嵌套层级,缓解了约束冲突,避免signal 9终止的情况。


内容的提问来源于stack exchange,提问作者Evman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 10:36:21