I’m trying to put together a simple text selection app for MacOS. It’s for my own use and is my first attempt at SwiftUI. The SubjectSelectionView will list approx 15 subjects and each subject navigates to a SubjectCommentsView which has 2 tabs, one for the two different types of comment (Observation / Recommendation). All of the comments are contained in the Model file. Text comments that are selected are copied to the clipboard and the view returns back to the SubjectSelectionView.
Very much based on NavStackIntro2 on YouTube - Big thanks to Stewart Lynch.
All is working as I want it to, but I am getting multiple “A navigationDestination for was declared earlier on the stack. Only the destination declared closest to the root view of the stack will be used.” and “List with selection: SelectionManagerBox<Never> tried to update multiple times per frame.” errors.
I can’t work out what is wrong. Can someone help me out please?
import SwiftUI
@main
struct AppEntry: App {
@StateObject var router = Router()
var body: some Scene {
WindowGroup {
SubjectSelectionView()
.environmentObject(router)
}
}
}
Router
import SwiftUI
class Router: ObservableObject {
@Published var path = NavigationPath()
func reset() {
path = NavigationPath()
}
}
Subject Selection View
import SwiftUI
struct SubjectSelectionView: View {
@EnvironmentObject var router: Router
var body: some View {
NavigationStack(path: $router.path) {
List(Subject.sample) { subject in
NavigationLink(value: subject) {
HStack {
Text(subject.name)
}
}
}
.navigationDestination(for: Subject.self) { subject in
SubjectCommentsView(vSubject: subject)
}
.navigationTitle("Q Subject selection...")
}
}
}
Subject Comments View
import SwiftUI
let pasteBoard = NSPasteboard.general
struct SubjectCommentsView: View {
@EnvironmentObject var router: Router
var vSubject: Subject
var body: some View {
VStack(alignment: .leading) {
HStack {
Text("Q - \(vSubject.name)")
.font(.headline)
}
TabView {
VStack{
// Observation Comments...(isObservation = True)
List(vSubject.comments.filter { $0.isObservation}) { subject in
NavigationLink(value: subject) {
Text (subject.name)
}
}
}
.tabItem {
Text ("Observation Comments...")
}
VStack{
// Recommendation Comments...(isObservation = False)
List(vSubject.comments.filter { !$0.isObservation}) { subject in
NavigationLink(value: subject) {
Text (subject.name)
}
}
}
.tabItem {
Text ("Recommendation Comments...")
}
}
Button("Return to Subjects...") {
router.reset()
}
}
.padding()
.navigationTitle("\(vSubject.name) Comments...")
.navigationDestination(for: Comment.self) { subject in
SubjectSelectionView()
// This prints out the correct comment
.onAppear(perform: {
let selectedComment = subject.name as String
// Call Copy Function
CommentCopy(selectedComment:selectedComment)
})
}
}
}
func CommentCopy(selectedComment: String){
let _ = print("Copied - \(selectedComment)")
pasteBoard.clearContents()
pasteBoard.writeObjects([selectedComment as NSString])
}
Models
import Foundation
struct Subject: Identifiable, Hashable {
var name: String
var comments: [Comment]
var id: String {name}
static var sample: [Subject] {
[
Subject(name: "Door", comments: Comment.all.filter{$0.subject == "Door"}),
Subject(name: "Window", comments: Comment.all.filter{$0.subject == "Window"}),
]}
}
// All Comments (Both Recommendation and Observation)
struct Comment: Identifiable, Hashable {
var name: String
var subject: String
var isObservation: Bool
var id: String {name}
static var all: [Comment] {
[
// Observations....
Comment (name: "Door Comment 4...", subject: "Door", isObservation: true),
Comment (name: "Window Comment 4...", subject: "Window", isObservation: true),
// Recommendations...
Comment (name: "Door Comment 5...", subject: "Door", isObservation: false),
Comment (name: "Window Comment 5...", subject: "Window", isObservation: false),
]
}
var matchingSubjects: [Comment] {
Self.all.filter {$0.subject == subject}
}
}