Search code examples
swiftuiuikitavfoundation

How to make a custom UIView Appear/Dissapear in SwiftUI


I have a CameraView in my app that I'd like to bring up whenever a button is to be presssed. It's a custom view that looks like this

// The CameraView
struct Camera: View { 
  @StateObject var model = CameraViewModel()
  
  @State var currentZoomFactor: CGFloat = 1.0
  @Binding var showCameraView: Bool
  
  // MARK: [main body starts here]
  var body: some View {
    GeometryReader { reader in
      ZStack {
        // This black background lies behind everything. 
        Color.black.edgesIgnoringSafeArea(.all)
        
        CameraViewfinder(session: model.session)
          .onAppear {
            model.configure()
          }
          .alert(isPresented: $model.showAlertError, content: {
            Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
              model.alertError.primaryAction?()
            }))
          })
          .scaledToFill()
          .ignoresSafeArea()
          .frame(width: reader.size.width,height: reader.size.height )
        
        // Buttons and controls on top of the CameraViewfinder
        VStack {
          HStack {
            Button {
              //
            } label: {
              Image(systemName: "xmark")
                .resizable()
                .frame(width: 20, height: 20)
                .tint(.white)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
            
            Spacer()
            
            flashButton
          }
          
          HStack {
            capturedPhotoThumbnail
            
            Spacer()
            
            captureButton
            
            Spacer()
            
            flipCameraButton
            
          }
          .padding([.horizontal, .bottom], 20)
          .frame(maxHeight: .infinity, alignment: .bottom)
        }
      } // [ZStack Ends Here]
    } // [Geometry Reader Ends here]
  } // [Main Body Ends here]

  // More view component code goes here but I've excluded it all for brevity (they don't add anything substantial to the question being asked. 
} // [End of CameraView] 

It contains a CameraViewfinder View which conforms to the UIViewRepresentable Protocol:

struct CameraViewfinder: UIViewRepresentable {
    class VideoPreviewView: UIView {
        override class var layerClass: AnyClass {
             AVCaptureVideoPreviewLayer.self
        }
        
        var videoPreviewLayer: AVCaptureVideoPreviewLayer {
            return layer as! AVCaptureVideoPreviewLayer
        }
    }
    
    let session: AVCaptureSession
    
    func makeUIView(context: Context) -> VideoPreviewView {
        let view = VideoPreviewView()
        view.backgroundColor = .black
        view.videoPreviewLayer.cornerRadius = 0
        view.videoPreviewLayer.session = session
        view.videoPreviewLayer.connection?.videoOrientation = .portrait

        return view
    }
    
    func updateUIView(_ uiView: VideoPreviewView, context: Context) {
        
    }
}

I wish to add a binding property to this camera view that allows me to toggle this view in and out of my screen like any other social media app would allow. Here's an example

@State var showCamera: Bool = false 
var body: some View { 
   mainTabView
     .overlay {
        CameraView(showCamera: $showCamera) 
     }
}

I understand that the code to achieve this must be written inside the updateUIView() method. Now, although I'm quite familiar with SwiftUI, I'm relatively inexperienced with UIKit, so any help on this and any helpful resources that could help me better code situations similar to this would be greatly appreciated.

Thank you.

EDIT: Made it clear that the first block of code is my CameraView. EDIT2: Added Example of how I'd like to use the CameraView in my App.


Solution

  • Judging by the way you would like to use it in the app, the issue seems to not be with the CameraViewFinder but rather with the way in which you want to present it.

    A proper SwiftUI way to achieve this would be to use a sheet like this:

    @State var showCamera: Bool = false 
    var body: some View { 
       mainTabView
         .sheet(isPresented: $showCamera) {
             CameraView()
                 .interactiveDismissDisabled() // Disables swipe to dismiss
         }
    }
    

    If you don't want to use the sheet presentation and would like to cover the whole screen instead, then you should use the .fullScreenCover() modifier like this.

    @State var showCamera: Bool = false 
    var body: some View { 
       mainTabView
         .overlay {
             CameraView()
                .fullScreenCover(isPresented: $showCamera)
         }
    }
    

    Either way you would need to somehow pass the state to your CameraView to allow the presented screen to set the state to false and therefore dismiss itself, e.g. with a button press.