skip to Main Content

I’m trying to understand how and when the .sheet and .fullScreenCover initializers are called. Below is a minimal reproducible example, where the first screen has 3 colored rectangles and the SecondView (shown via .fullScreenCover) has 1 rectangle that changes color based on the selected color from the first screen.

  • When the app first loads, the color is set to .gray.
  • If I tap on the green rectangle, SecondView presents with a gray rectangle. (ie. the color DIDN’T change correctly).
  • If I then dismiss the SecondView and tap on the red rectangle, the SecondView presents with a red rectangle. (ie. the color DID change correctly.)

So, I’m wondering why this set up does NOT work on the initial load, but does work on the 2nd/3rd try?

Note: I understand this can be solved by changing the ‘let selectedColor’ to a @Binding variable, that’s not what I’m asking.

Code:

import SwiftUI

    struct SegueTest: View {
        
        @State var showSheet: Bool = false
        @State var color: Color = .gray
        
        var body: some View {
            HStack {
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.red)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        color = .red
                        showSheet.toggle()
                    }
                
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.green)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        color = .green
                        showSheet.toggle()
                    }
    
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.orange)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        color = .orange
                        showSheet.toggle()
                    }
    
            }
            .fullScreenCover(isPresented: $showSheet, content: {
                SecondView(selectedColor: color)
            })
        }
    }
    
    struct SecondView: View {
        
        @Environment(.presentationMode) var presentationMode
        let selectedColor: Color // Should change to @Binding
        
        var body: some View {
            ZStack {
                Color.black.edgesIgnoringSafeArea(.all)
                
                RoundedRectangle(cornerRadius: 25)
                    .fill(selectedColor)
                    .frame(width: 300, height: 300)
            }
            .onTapGesture {
                presentationMode.wrappedValue.dismiss()
            }
        }
    
    }
    
    struct SegueTest_Previews: PreviewProvider {
        static var previews: some View {
            SegueTest()
        }
    }

2

Answers


  1. See comments and print statements. Especially the red

    import SwiftUI
    
    struct SegueTest: View {
        
        @State var showSheet: Bool = false{
            didSet{
                print("showSheet :: didSet")
            }
            willSet{
                print("showSheet :: willSet")
            }
        }
        @State var color: Color = .gray{
            didSet{
                print("color :: didSet :: (color.description)")
            }
            willSet{
                print("color :: willSet :: (color.description)")
            }
        }
        @State var refresh: Bool = false
        init(){
            print("SegueTest " + #function)
        }
        var body: some View {
            print(#function)
            return HStack {
                //Just to see what happens when you recreate the View
                //Text(refresh.description)
                Text(color.description)
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.red)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        print("SegueTest :: onTapGesture :: red")
                        //Changing the color
                        color = .red
                        //Refreshed SegueTest reloads function
                        //refresh.toggle()
                        showSheet.toggle()
                    }
                
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.green)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        print("SegueTest :: onTapGesture :: green")
                        //Changing the color
                        color = .green
                        showSheet.toggle()
                    }
                
                RoundedRectangle(cornerRadius: 25)
                    .fill(Color.orange)
                    .frame(width: 100, height: 100)
                    .onTapGesture {
                        print("SegueTest :: onTapGesture :: orange")
                        //Changing the color
                        color = .orange
                        showSheet.toggle()
                    }
                
            }
            //This part is likely created when SegueTest is created and since a struct is immutable it keeps the original value
            .fullScreenCover(isPresented: $showSheet, content: {
                SecondView(selectedColor: color)
            })
        }
    }
    
    struct SecondView: View {
        
        @Environment(.presentationMode) var presentationMode
        //struct is immutable
        let selectedColor: Color // Should change to @Binding
        init(selectedColor: Color){
            print("SecondView " + #function)
            self.selectedColor = selectedColor
            print("SecondView :: struct :: selectedColor = (self.selectedColor.description)" )
            print("SecondView :: parameter :: selectedColor = (selectedColor.description)" )
        }
        var body: some View {
            ZStack {
                Color.black.edgesIgnoringSafeArea(.all)
                
                RoundedRectangle(cornerRadius: 25)
                    .fill(selectedColor)
                    .frame(width: 300, height: 300)
            }
            .onTapGesture {
                presentationMode.wrappedValue.dismiss()
            }
        }
        
    }
    
    struct SegueTest_Previews: PreviewProvider {
        static var previews: some View {
            SegueTest()
        }
    }
    
    Login or Signup to reply.
  2. The problem is that you are not using your @State color inside SegueView, which will not reload your view on State change. Just include your @State somewhere in the code, which will force it to rerender at then update the sheet, with the correct color.

    RoundedRectangle(cornerRadius: 25)
        .fill(color == .red ? Color.red : Color.red) //<< just use dump color variable here
    

    If you do not include your color state somewhere in the code, it won’t re render your SegueView, hence you still pass the old color gray to your SecondView.

    Or even pass your colors as Binding your your Second View..

    SecondView(selectedColor: $color)
    
    @Binding var selectedColor: Color 
     
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search