SwiftUI losing reference while trying to reuse a UIViewRepresentable on different views

I’m trying to reuse a UIKit view on SwiftUI with a UIViewRepresentable.
Whenever i tap the representable i want to go to a fullScreenCover and reuse that same UIView.
Somehow, when i close the fullscreenView, the UIView is missing.
I’ve tried several approaches but i couldn’t make it work,
Can some one point me in the right direction, is there something i’m missing to see?

InitialLoad
Fullscreen
ReturnFromFullscreen

struct ContentView: View {
   @State var isFullscreen: Bool = false
   /// Array of UIView objects
   var uiViewArray: [CustomUIView] = [CustomUIView(text: "1"), CustomUIView(text: "2"), CustomUIView(text: "3")]
   let cardWidth: CGFloat = 200
   let cardHeight: CGFloat = 200
   
   @StateObject var viewManager = ViewManager()

   var body: some View {
       return VStack {
           ScrollView {
               ForEach(uiViewArray, id: \.id) { uiView in
                   LabelViewRepresentable(customUIView: uiView)
                       .frame(width: cardWidth,
                              height: cardHeight)
                       .onTapGesture(count: 2) {
                           viewManager.selectedFullscreenView = uiView
                           isFullscreen.toggle()
                       }
               }
           }
       }
       .background(Color.gray)
       .fullScreenCover(isPresented: $isFullscreen,
                     content: {
           FullScreenView(isFullscreen: $isFullscreen,
                          uiView: viewManager.selectedFullscreenView)
       })
    
   }
}
struct FullScreenView: View {
    @Binding var isFullscreen: Bool
    var uiView: CustomUIView?
         
    var body: some View {
        VStack {
            Button(action: {
                isFullscreen.toggle()
            }, label: {
                Text("Close")
            })
            if uiView != nil {
                LabelViewRepresentable(customUIView: uiView!)
            }
        }
    }
}
struct LabelViewRepresentable: UIViewRepresentable {
    var customUIView: CustomUIView
    
    init(customUIView: CustomUIView) {
        self.customUIView = customUIView
    }

    func makeUIView(context: Context) -> CustomUIView {
       uiView.backgroundColor = .green
       return customUIView
    }

    func updateUIView(_ uiView: CustomUIView, context: Context) { }
}
class CustomUIView: UIView {
    var text: String
    var id = UUID().uuidString
    
    init(text: String) {
        self.text = text
        super.init(frame: .zero)
        let label = UILabel()
        label.frame = .init(origin: .zero,
                            size: CGSize(width: 100, height: 100))
        label.text = self.text
        self.addSubview(label)
    }
    
    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class ViewManager: ObservableObject  {
    var selectedFullscreenView: CustomUIView?
}

>Solution :

The UIView can only be added once to a view hierarchy. Once it’s added to the full screen view, it’s taken out of the original hierarchy and it has no signal to add itself back in when the full screen view is dismissed.

You can avoid this issue by conditionally displaying it in the original ScrollView. Note that I’ve added a Spacer to take its space and preserve the scroll position while the real UIView is omitted from the hierarchy.

ForEach(uiViewArray, id: \.id) { uiView in
    Group {
        if !isFullscreen {
            LabelViewRepresentable(customUIView: uiView)
        } else {
            Spacer()
        }
    }
    .frame(width: cardWidth,
           height: cardHeight)
    .onTapGesture(count: 2) {
        viewManager.selectedFullscreenView = uiView
        isFullscreen.toggle()
    }
}

Leave a Reply