Search code examples
iosswiftuiviewuianimation

View size is automatically being set to window size, when one of window's subview is removed


I have two views one of them is the mainVideoView (Grey color) and the other is the subVideoView (red color). Both of them are the subViews of UIApplication.shared.keyWindow.

When I try and minimise them (using func minimiseOrMaximiseViews, they get minimised (as shown in the below image).

After which, I would like to remove the subVideoView from the window. The moment I try and remove the subVideoView (using func removeSubVideoViewFromVideoView() called from func minimiseOrMaximiseViews), the mainVideoView enlarges to the full screen size, I am not sure why this is happening, I want it to stay at the same size.

Could someone please advise/ suggest how I could achieve this ?

enter image description here

This is how I am setting up the views

func configureVideoView(){
  videoView.backgroundColor    = UIColor.darkGray
  videoView.tag                = 0    // To identify view during animation

  // ADDING TAP GESTURE TO MAXIMISE THE VIEW WHEN ITS SMALL
  let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:)))
  videoView.addGestureRecognizer(tapGestureRecognizer)

  // ADDING PAN GESTURE RECOGNISER TO MAKE VIDEOVIEW MOVABLE INSIDE PARENT VIEW
  videoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))


  guard let window = UIApplication.shared.keyWindow else{
    return
  }

  window.addSubview(videoView)

  videoView.translatesAutoresizingMaskIntoConstraints = false

  let viewsDict = ["videoView" : videoView] as [String : Any]

  // SETTING CONSTRAINTS
   window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[videoView]|", options: [], metrics: nil, views: viewsDict))
   window.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[videoView]|", options: [], metrics: nil, views: viewsDict))


  window.layoutIfNeeded() // Lays out subviews immediately
  window.bringSubview(toFront: videoView) // To bring subView to front

  print("Videoview constraints in configureVideoView \(videoView.constraints)")

}

func configureSubVideoView(){

    subVideoView.backgroundColor    =   UIColor.red
    subVideoView.tag                =   1    // To identify view during animation
    subVideoView.isHidden           = true


    // Adding Pan Gesture recogniser to make subVideoView movable inside parentview
    subVideoView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragView)))

    // Constraining subVideoView to window to ensure that minimising and maximising animation works properly
    constrainSubVideoViewToWindow()

}

func constrainSubVideoViewToWindow(){

    //self.subVideoView.setNeedsLayout()
    guard let window = UIApplication.shared.keyWindow else{
        print("Window does not exist")
        return
    }

    guard !window.subviews.contains(subVideoView)else{  // Does not allow to go through the below code if the window already contains subVideoView
        return
    }

    if self.videoView.subviews.contains(subVideoView){ // If videoView contains subVideoView remove subVideoView

        subVideoView.removeFromSuperview()

    }

    window.addSubview(subVideoView)
        // Default constraints to ensure that the subVideoView is initialised with maxSubVideoViewWidth & maxSubVideoViewHeight and is positioned above buttons

    let bottomOffset    =   buttonDiameter + buttonStackViewBottomPadding + padding

    subVideoView.translatesAutoresizingMaskIntoConstraints = false

    let widthConstraint = NSLayoutConstraint(item: subVideoView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewWidth)

    let heightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: maxSubVideoViewHeight)

    let rightConstraint = NSLayoutConstraint(item: subVideoView, attribute: .trailing, relatedBy: .equal, toItem: window, attribute: .trailing, multiplier: 1.0, constant: -padding)

    let bottomConstraint = NSLayoutConstraint(item: subVideoView, attribute: .bottom, relatedBy: .equal, toItem: window, attribute: .bottom, multiplier: 1.0, constant: -bottomOffset)

    var constraintsArray = [NSLayoutConstraint]()
    constraintsArray.append(widthConstraint)
    constraintsArray.append(heightConstraint)
    constraintsArray.append(rightConstraint)
    constraintsArray.append(bottomConstraint)

    window.addConstraints(constraintsArray)

    window.layoutIfNeeded() // Lays out subviews immediately
    subVideoView.setViewCornerRadius()
    window.bringSubview(toFront: subVideoView) // To bring subView to front

}

This is how I am animating views and removing subVideoView

func minimiseOrMaximiseViews(animationType: String){

    let window = UIApplication.shared.keyWindow

    let buttonStackViewHeight = buttonsStackView.frame.height

    if animationType == "maximiseView" {
        constrainSubVideoViewToWindow()

    }


    UIView.animate(withDuration: 0.3, delay: 0, options: [],

        animations: { [unowned self] in  // "[unowned self] in" added to avoid strong reference cycles,
            switch animationType {

            case "minimiseView" :

                self.hideControls()

                // Minimising self i.e videoView
                self.videoView.frame = CGRect(x:      self.mainScreenWidth - self.videoViewWidth - self.padding,
                                              y:      self.mainScreenHeight - self.videoViewHeight - self.padding,
                                              width:  self.videoViewWidth,
                                              height: self.videoViewHeight)


                // Minimising subVideoView
                self.subVideoView.frame =   CGRect(x:       self.mainScreenWidth - self.minSubVideoViewWidth - self.padding * 2,
                                                   y:       self.mainScreenHeight - self.minSubVideoViewHeight - self.padding * 2,
                                                   width:   self.minSubVideoViewWidth,
                                                   height:  self.minSubVideoViewHeight)
                window?.layoutIfNeeded()


                self.videoView.setViewCornerRadius()
                self.subVideoView.setViewCornerRadius()

                print("self.subVideoView.frame AFTER setting: \(self.subVideoView.frame)")



            default:
                break
            }
    }) { [unowned self] (finished: Bool) in   // This will be called when the animation completes, and its finished value will be true

       if animationType == "minimiseView" {

            //  **** Removing subVideoView from videoView ****
            self.removeSubVideoViewFromVideoView() 

        }
    }


}

func removeSubVideoViewFromVideoView(){

    //self.subVideoView.setNeedsLayout()

    guard let window = UIApplication.shared.keyWindow else{
        return
    }

    guard !self.videoView.subviews.contains(subVideoView)else{  // Does not allow to go through the below code if the videoView already contains subVideoView
        return
    }

    if window.subviews.contains(subVideoView) {     // removing subVideoView from window
        subVideoView.removeFromSuperview() // *** CAUSE OF ISSUE ***

    }

    guard !window.subviews.contains(subVideoView) else{
        return
    }

    videoView.addSubview(subVideoView)
}

Solution

  • The videoView and subVideoView are layout by Auto Layout. In Auto Layout if you want to change the frame, you have to update the constraints.

    Calling removeFromSuperView will removes any constraints that refer to the view you are removing, or that refer to any view in the subtree of the view you are removing. This means the auto layout system will to rearrange views.