UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Need help on day 50: hiding "extra Frosting" and showing will trigger unwanted animation

Forums > 100 Days of SwiftUI

Dear all,

maybe one of you can point me in the right direction. I've tried googling a solution, but maybe I'm lacking an short adequate description of my problem.

So I've re-created the interface with the "Special Requests" showing and hiding both options "Extra Frosting" and "Add Sprinkles". Also I've added a didSet property observer so set both extraFrosting and addSprinkles to false, whenever specialRequests are disabled (did change that to a willSet, but this wouldn't change the behaviour I'm facing).

Section {
      Toggle("Special Requests", isOn: $order.orderInfo.specialRequests.animation())

      if order.orderInfo.specialRequests {
          Toggle("Extra Frosting", isOn: $order.orderInfo.extraFrosting)
          Toggle("Add Sprinkles", isOn: $order.orderInfo.addSprinkles)
      }
  }
var specialRequests = false {
    willSet {
        if newValue == false {
            extraFrosting = false
            addSprinkles = false
        }
    }
}

Now my issue: 1) I enable special Requests (all fine) 2) I enable extraFrosting (all fine) 3) I disable special Requests (all fine), the extraFrosting modifier gets correctly set to false (as expected) 4) Now I enable special Requests again... the views will show, but there is an animation for extra Frosting which visibily sets the toggle from on to off.

How can I prevent this visible change from on to off for the extra Frosting toggle?

So far I've tried putting the respective variables inside the class or inside a struct, no change. I would assume this happends because the extraFrosting View isn't redrawn until it is shown again.

But after that I'm lost. Any hint into the right direction would be greatly appreciated.

Kind regards, Heiko

2      

@Heiko asks for help.

Any hint into the right direction would be greatly appreciated.

There may be another way to implement property observers. This solutions below uses the .onChange view modifier, rather than a didSet property observer. Paste this code into Playgrounds and convince yourself this may be a solution to your question.

Also, I am a fan of adding an extra VStack to my views when I have a number of @State properties. I reflect the @State properties in the VStack. This gives me an indication of each @State's value and I can compare them to the visual state of the view. It's useful for establishing your logic, and whilst debugging.

// Paste into Playgrounds and experiment with different settings.
import SwiftUI
import PlaygroundSupport

struct SpecialRequestsView: View {
    @State private var showSpecialRequests = true
    @State private var extraFrosting       = false
    @State private var addSprinkles        = false

    var body: some View {
        VStack {
            Section("Struct State") {
                VStack {
                    // Add this to show your struct's state vars whilst debugging
                    Text("Show Extras   is: \(showSpecialRequests ? "TRUE" : "FALSE" )")
                    Text("extraFrosting is: \(extraFrosting       ? "TRUE" : "FALSE" )")
                    Text("addSprinkles  is: \(addSprinkles        ? "TRUE" : "FALSE" )")
                    Divider().padding(.vertical)
                }.frame(width: 300)
            }.padding(.top)
            Section {
                VStack {
                    Toggle("Special Requests", isOn: $showSpecialRequests.animation())
                    if showSpecialRequests {
                        Divider()
                        Toggle("Extra Frosting", isOn: $extraFrosting )
                        Toggle("Add Sprinkles",  isOn: $addSprinkles  )
                    }
                    Spacer()
                }.onChange(of: showSpecialRequests) { changedTo in
                    if changedTo == false {
                        // User didn't want any specials. So change these to FALSE
                        extraFrosting = false
                        addSprinkles  = false
                    }
                }
            }.padding()
        }.frame(height: 400)
    }
}

PlaygroundPage.current.setLiveView(SpecialRequestsView() )  // <- This allows you to experiment with views in Playgrounds

2      

@Obelix

Thank you for taking your time to answer my question. I can confirm, that in the playground, both toggles for "Extra Frosting" and "Add Sprinkles" do work as expected and as you have described.

With e.g. Extra Frosting toggled on, when I disable "Special Requests", both are disabled immediately and hidden, and once Special Requests are enabled again, both toggles start to appear again and start with being toggled off.

Now if I run this code in Xcode for iOS 16.1 in my case (with your .onChange modifier), unfortunately I still get the same unexpected result as before with the didSet property observer: I enable Extra Frosting, disable Special Requests and both Extra Frosting and Add Sprinkles are being faded out (but not "visually" disabled - the variables itself are set to false). Once I enable Special Requests again, both additional toggled start to appear again but only now the animate into a "toggled off" state. This btw happens also if I disable the animation() on the Special Requests toggle. Those extra toggles with suddenly appear and only the the toggles are animated into toggle-off state (while the variables itself already have been set to false)

I have tried out Paul's code and the effect is the same. Is this maybe a change in iOS or Xcode? I think Xcode 13 and iOS 15.x was recent when Paul made his videos, but I'm using Xcode 14 with iOS 16.x... tried changing to iOS 15.0, but the result is still the same.

Kind regards, Heiko

2      

Your description is complete, so I do not doubt its accuracy.

However, without seeing additional code, this may be difficult to diagnose. I feel like there's code somewhere, perhaps in .onAppear() ? that changes the state of your vars.

Did you add the Struct State section to your code?

Section("Struct State") {
    VStack {
        // Add this to show your struct's state vars whilst debugging
        Text("Show Extras   is: \(showSpecialRequests ? "TRUE" : "FALSE" )")
             .foregroundColor(showSpecialRequests ? .green : .red)  // <- This changes color too.
        Text("extraFrosting is: \(extraFrosting       ? "TRUE" : "FALSE" )")
        Text("addSprinkles  is: \(addSprinkles        ? "TRUE" : "FALSE" )")
    }.frame(width: 300)
}.padding(.top)

Also, when you run this on your actual iPhone do you see this behavior? It may be fine on a real device but buggy in simulation mode.

Slow Animations

Finally, when you run your application in the simulator, please look at the Debug menu. There is an option for Slow Animations As you tap the toggles, you should see the toggle change to green, and at the same time see the text change in your "Struct State" section. Slowing the animations may provide extra clues.

2      

@Obelix

Good point on missing code. I've created a MVP which shows the unexpected behaviour. It does bug me a little, as it's not only different from Paul's video, but also might be confusing to the user.

The "toggle off animation" for toggleTwo is visible in Xcode canvas, the simulator and an actual device. Tested with iOS target 16.2 on canvas and simulator and 16.1 on iPhone.

import SwiftUI

struct ContentView: View {
    @State var toggleOne = false
    @State var toggleTwo = false

    var body: some View {
        Form {
            Section {
                Text("toggleOne: \(String(toggleOne))")
                Text("toggleTwo: \(String(toggleTwo))")
            }
            Section {
                Toggle("Special Requests", isOn: $toggleOne.animation())

                if toggleOne {
                    Toggle("Extra Frosting", isOn: $toggleTwo)
                }
            }
            .onChange(of: toggleOne) { changedTo in
                if toggleOne == false {
                    toggleTwo = false
                }
            }
        }
    }
}

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

As for the animation:

(1) I click the first button ton enable it, 3 things happen

(a) first toggles animations on an "on" state

(b) second toggle is being animated to be visible

(c) toggleOne variables switched on true

(2) I click the second toggle to enable it, 2 things happen

(a) variable toggleTwo is set to true

(b) second toggle is animated to an "on" state

(3) I click the first toggle to disable it, 4 things happend and (3) (e) is not to be seen

(a) toggleOne is set to false

(b) toggleTwo is set to false

(c) first toggle is animated to an "off" state

(d) second toggle is animated to fade out

I'm missing:

(e) second toggle is animated to an "off" state (similar to (3) (c) )

(4) Now I click on the first button again to enable it once more, 3 things happen:

(a) toggleOne variabls is set to true (b) second toggle is faded in (c) second toggle is now animated to "off", while fading in <-- this I would expect not to happen

To me it seems the animation for second toggle to be toggled off is happening at the wrong time / trigger.

Thank you for your patience and your will to help @Obelix

Kind regards, Heiko

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.