diff --git a/ios/Maturita/AppModel.swift b/ios/Maturita/AppModel.swift index d4bcadf..a2686f8 100644 --- a/ios/Maturita/AppModel.swift +++ b/ios/Maturita/AppModel.swift @@ -51,13 +51,7 @@ final class AppModel: ObservableObject { tab = .practice practicePath = [] default: - tab = .practice - if practicePath.last == r { return } - if practicePath.isEmpty { - practicePath = r.stack - } else { - practicePath.append(r) - } + applyPracticeRoute(r, replace: practicePath.isEmpty) } } @@ -75,8 +69,25 @@ final class AppModel: ObservableObject { tab = .practice practicePath = [] default: - tab = .practice - practicePath = r.stack + // Switching tabs and assigning a deep NavigationStack path in the + // same turn crashes SwiftUI on macOS (boundPathChange / unexpectedError). + applyPracticeRoute(r, replace: true) + } + } + + /// Sets the practice tab path safely, especially when jumping from Search. + private func applyPracticeRoute(_ r: Route, replace: Bool) { + let stack = replace ? r.stack : (practicePath + [r]) + if !replace, practicePath.last == r { return } + let switching = tab != .practice + tab = .practice + if switching || replace { + practicePath = [] + DispatchQueue.main.async { [weak self] in + self?.practicePath = stack + } + } else { + practicePath = stack } } diff --git a/ios/Maturita/Route.swift b/ios/Maturita/Route.swift index 04845f9..2084de6 100644 --- a/ios/Maturita/Route.swift +++ b/ios/Maturita/Route.swift @@ -54,8 +54,14 @@ enum Route: Hashable { if id == "1984" { return "cetba1984" } if id == "fuks" { return "cetbaFuks" } return "cetba_" + id.replacingOccurrences(of: "-", with: "_") - case .bookQuiz(let id): return id == "1984" ? "cetba1984quiz" : "cetbaFuksQuiz" - case .bookPlot(let id): return id == "1984" ? "cetba1984dej" : "cetbaFuksDej" + case .bookQuiz(let id): + if id == "1984" { return "cetba1984quiz" } + if id == "fuks" { return "cetbaFuksQuiz" } + return "cetba_" + id.replacingOccurrences(of: "-", with: "_") + "_quiz" + case .bookPlot(let id): + if id == "1984" { return "cetba1984dej" } + if id == "fuks" { return "cetbaFuksDej" } + return "cetba_" + id.replacingOccurrences(of: "-", with: "_") + "_dej" } } @@ -123,6 +129,14 @@ func routeFromPage(_ page: String) -> Route? { case "cetbaFuksQuiz": return .bookQuiz("fuks") case "cetbaFuksDej": return .bookPlot("fuks") default: + if page.hasPrefix("cetba_"), page.hasSuffix("_quiz") { + let slug = String(page.dropFirst(6).dropLast(5)).replacingOccurrences(of: "_", with: "-") + return .bookQuiz(slug) + } + if page.hasPrefix("cetba_"), page.hasSuffix("_dej") { + let slug = String(page.dropFirst(6).dropLast(4)).replacingOccurrences(of: "_", with: "-") + return .bookPlot(slug) + } if page.hasPrefix("cetba_") { let slug = String(page.dropFirst(6)).replacingOccurrences(of: "_", with: "-") return .book(slug) diff --git a/ios/Maturita/UI/Screens.swift b/ios/Maturita/UI/Screens.swift index f068b09..26ec309 100644 --- a/ios/Maturita/UI/Screens.swift +++ b/ios/Maturita/UI/Screens.swift @@ -939,7 +939,7 @@ struct BookScreen: View { } ForEach(Array(b.arr("notes").enumerated()), id: \.offset) { _, note in Section(note.str("title")) { - ForEach(note.strs("lines"), id: \.self) { line in + ForEach(Array(note.strs("lines").enumerated()), id: \.offset) { _, line in Text(line) } }