skip to Main Content

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?
}

2

Answers


  1. 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()
        }
    }
    
    Login or Signup to reply.
  2. makeUIView needs to init it, e.g.

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

    You also need to implement updateUIView to use the struct’s vars to update the uiView with any changes, e.g.

    let text: String
    
    func updateUIView(_ uiView: CustomUIView, context: Context) {
        if uiView.text != text {
            uiView.text = text
        }
    }
    

    Think of the UIViewRepresentable like a configuration value that constantly changes and you use to update the same UIView object. SwiftUI keeps track of what UIView object to configure based on its position in the View hierarchy (called its identity) and passes it in to updateUIView so you don’t need to hold onto it yourself.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search