'BUG navigating from a view to another in SwiftUI
I have a problem and I really don't know how can i fix this. When I'm pressing the button, I want to navigate to the next view, but at the last view, it didn't work and it pushes me out to the MainView. So, in the CartView, if the onboardingState == 1, it will create an Order and the status will be changed to "pending", and will show the WaitingOrderView(), but is not working as expected.
I will put a view under the code that I will share right know.
struct CartView: View {
@EnvironmentObject var syncViewModel : SyncViewModel
@State var onboardingState: Int = 0
@State var index = 0
@State private var timer: AnyCancellable?
var body: some View {
ZStack {
switch onboardingState {
case 0 :
VStack {
welcomeSection
Spacer()
bottomButton
.padding(30)
}
case 1 :
VStack {
welcomeSection2
Spacer()
bottomButton
.padding(30)
}
case 2 :
VStack {
detailOrder
Spacer()
bottomButton
.padding(30)
}
default:
EmptyView()
}
}
}
}
extension CartView {
private var bottomButton : some View {
Text("Continue")
.font(.headline)
.foregroundColor(.white)
.frame(height: 55)
.frame(maxWidth: .infinity)
.background(Color.onboardingColor)
.cornerRadius(20)
.onTapGesture {
handleNextButtonPressed()
}
}
private var welcomeSection: some View {
PaymentView()
}
private var welcomeSection2: some View {
LivrareView()
}
@ViewBuilder
private var detailOrder: some View {
ZStack {
if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.pending.rawValue
})?.id
{
WaitingOrderView()
}
else
{
EmptyView()
}
}
}
}
extension CartView {
func handleNextButtonPressed() {
if onboardingState == 1 {
syncViewModel.createOrder()
onboardingState += 1
}
else {
withAnimation(.spring()) {
onboardingState += 1
}
}
}
}
struct MainView: View {
@EnvironmentObject var syncViewModel : SyncViewModel
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [])
private var menus : FetchedResults<LocalMenu>
var body: some View {
TabView{
NavigationView{
MeniuriView()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolbarButtons(numberOfProducts: menus.count)
}
ToolbarItem(placement: .navigationBarLeading) {
Text(Texts.mainViewText1)
.font(.system(size: 24))
.fontWeight(.bold)
.padding()
}
}
}
.tabItem {
Text(Texts.mainViewText2)
Image(systemName: "fork.knife")
}
NavigationView {
AlteleView()
}
.tabItem {
Text(Texts.mainViewText3)
Image("altele", bundle: Bundle.main)
}
}
.accentColor(Color.tabItemColor)
}
}
struct ToolbarButtons: View {
@EnvironmentObject var syncViewModel : SyncViewModel
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [])
private var cartOrder : FetchedResults<CartOrders>
var numberOfProducts : Int
var body: some View {
ZStack {
Spacer()
if syncViewModel._order.status == 0 && syncViewModel._order.id == 0{
NavigationLink(destination: CartView()
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Text(Texts.cartText)
.font(.system(size: 24))
.bold()
}
}
, label: {
Image("otherIcon")
.padding(.top, 5)
}
)
}
else if syncViewModel._order.id == cartOrder.first?.id ?? Int32(syncViewModel._order.id) {
NavigationLink {
WaitingOrderView()
} label: {
ZStack(alignment: .topTrailing) {
Image("cartIcon")
.padding(.top, 5)
if numberOfProducts > 0 {
Text("\(numberOfProducts)")
.font(.caption2).bold()
.foregroundColor(.white)
.frame(width: 15, height: 15)
.background(Color.tabItemColor)
.cornerRadius(50)
.offset(x: 10, y: -8)
}
}
}
}
}
}
}
struct WaitingOrderView {
@EnvironmentObject var syncViewModel : SyncViewModel
let transition: AnyTransition = .asymmetric(
insertion: .move(edge: .trailing),
removal: .move(edge: .leading))
var body: some View {
ZStack {
if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.pending.rawValue
})?.id {
ChefView()
.environmentObject(syncViewModel)
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.accepted.rawValue
})?.id
{
OrderConfirmedView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.readyForDelivery.rawValue
})?.id {
OrderReadyForDeliveryView()
}
else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.onTheWay.rawValue
})?.id {
OrderOnTheWayView()
} else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.completed.rawValue
})?.id {
OrderCompletedView()
} else if syncViewModel._order.status == syncViewModel.statusList.first(where: { status in
status.key == StatusKey.canceled.rawValue
})?.id {
OrderCanceledView()
}
}
}
Solution 1:[1]
These might not be the reason for the exact bug but your use of SwiftUI is non-standard and will likely cause you problems. In browsing your code here is a list of issues I noticed:
- We don't use view model objects in SwiftUI, we use
@Stateand we can use a custom struct to make some logic more testable and the whole reason is to take advantage of Swift's value semantics - see Managing User Interface State. If you use objects instead of value types you will have major problems. Also worth watching Data Essentials in SwiftUI WWDC 2020 the first half is about view state and shows the use of custom structs, e.g.struct EditorConfig. - You are using computed vars e.g.
private var detailOrder: some Viewinstead of custom Views, e.g.DetailOrderView. This breaks SwiftUI's dependency tracking. You are supposed to break up your hierarchy into small views with only a few lets or states sobodywill be recomputed less often, which is called having tighter invalidation. ADetailOrderViewcould have let orderStatus so although it might be init many times, its body is only computed when orderStatus is different. In your current code your entire massivebodyis recomputed whenorderStatuschanges. var numberOfProducts : Intshould be let.- We try not to use
ifinside body which compiles to a_ConditionalViewand breaks SwiftUI's structural identity. See Demystify SwiftUI WWDC 2021. You can avoid this with something likeText(orderStatus.complete ? "Complete" : "In-progress")so there is always a Text just with different params. We can also use modifiers conditionally by using the usual.automaticparam in the else case.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |

