swiftuikitviewcontrollerstrong-references

How to dispose from UIView subviews on one ViewController when navigating away from it


I'm starting to learn Swift and decided to build an app but without storyboard.

My SceneDelegate scene function instantiates a TabBarController

window = UIWindow(frame: UIScreen.main.bounds)
let tb = TabBarController()

window?.rootViewController = tb
window?.makeKeyAndVisible()
window?.windowScene = windowSceme

I have a TabBarController that extends from UITabBarController which pretty much styles the tab bar and sets it all up

For each item of the Tabbar I have a ViewController. For the purpose of this question I'm going to specify the first "Home".

In Home, which extends ViewController, I have the following

let homePageView = HomePageView()
override func viewWillLayoutSubviews() {
   navigationController?.isNavigationBarHidden = true
}

override func viewDidAppear(_ animated: Bool) {
   super.viewDidAppear(true)
   homePageView.setupHomePage()
   view.addSubview(homePageView)
}

override func viewWillDisappear(_ animated: Bool) {
   homePageView.dispose()
}

The Controller is pretty much only in charge of calling whatever is going to be displayed on the screen within HomePageView(), the main component. This last, holds two more views. One carrousel HomePageCarrousel() and one header HomePageSocialUp(). This view instantiates the two latter referred to and sets the layout programmatically and adds them as subviews. It also includes a dispose function that sets the instantiated classes to nil such as --> Instantiate - homePageCarrousel = HomePageCarrousel() and the dispose function has homePageCarrousel = nil

That works perfectly fine but when I navigate away from the current view controller via the tab bar and navigate back to it, now I have two instances of HomePageCarrousel() and HomePageSocialUp() within HomeView

Hierarchy

I'm probably holding a strong reference somewhere but I can't figure out when. Could someone point me to where should I look to debug it or what might be that is creating the issue.

I'm also providing the code for the two views duplicated in case the issue is there

HomePageSocialUp

class HomePageSocialUp: UIView {
   
   let logo = UIImage(named: "LogoSmiles")
   let socialImages: [UIImage] = [UIImage(named: "tripadvisor")!, UIImage(named: "instagram")!, UIImage(named: "facebook")!, UIImage(named: "linkedin")!]
   
   func setupHeaderCircle() {
      guard let insetTop = UIApplication.shared.keyWindow?.safeAreaInsets.top else {return}
      let circlePath = UIBezierPath(arcCenter: CGPoint(x: UIScreen.main.bounds.width / 2, y: insetTop + 60), radius: CGFloat(90), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
      
      let shapeLayer = CAShapeLayer()
      shapeLayer.path = circlePath.cgPath
      
      shapeLayer.fillColor = UIColor.white.cgColor
      
      layer.addSublayer(shapeLayer)
   }
   
   func setupSocialHeader() {
      setupHeaderCircle()
      layer.masksToBounds = true;
      
      backgroundColor = UIColor.TintColor
      layer.shadowColor = UIColor.lightGray.cgColor
      layer.shadowOpacity = 0.8
      layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
      layer.shadowRadius = 3.0
      layer.masksToBounds = false
      frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 280)
      
      let imageView = UIImageView()
      guard let logo = logo else {return}
      guard let insetTop = UIApplication.shared.keyWindow?.safeAreaInsets.top else {return}
      imageView.frame = CGRect(x: CGFloat(Int((UIScreen.main.bounds.width - (logo.size.width)))) / 2, y: 120 - (logo.size.width / 2), width: logo.size.width, height: logo.size.height)
      imageView.image = logo
      addSubview(imageView)
      
   }
}

HomePageCarrousel

class HomePageCarrousel: UIScrollView {
   
   
   var images: [UIImage]?
   
   var originX = 0
   var numberOfIterations = 0
   var timer: Timer?
   
   func setupCarrousel() {
      
      let x = (Int(UIScreen.main.bounds.width) - (Int(UIScreen.main.bounds.width) - 60)) / 2
      frame = CGRect(x: x, y: (Int(frame.origin.y) - 350) / 2, width: Int(UIScreen.main.bounds.width) - 60, height: 350)
      
      let newImage = textToImage(drawText: "Creating Smiles in unique places.", frame: frame, inImage: UIImage(named: "smiles1")!, atPoint: CGPoint(x: frame.origin.x + 20, y: frame.height - 20))
      
      images = [newImage, UIImage(named: "smiles2")!]
      
      guard timer == nil else { return }
      timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(startScrolling), userInfo: nil, repeats: true)
      
      guard let imageCount = images?.count, let images = images else { return }
      contentSize = CGSize(width: frame.width * CGFloat(images.count), height: frame.height)
      for image in 0..<imageCount {
         let imageView = UIImageView()
         imageView.image = images[image]
         imageView.contentMode = .scaleAspectFill
         imageView.clipsToBounds = true
         
         let xPosition = frame.width * CGFloat(image)
         imageView.frame = CGRect(x: xPosition, y: 0, width: frame.width, height: frame.height)
         addSubview(imageView)
      }
      
      timer?.fire()
   }
   
   func textToImage(drawText text: String, frame: CGRect, inImage image: UIImage, atPoint point: CGPoint) -> UIImage {
      let textColor = UIColor.white
      let textFont = UIFont(name: "Helvetica Bold" , size: 12)!
      
      let scale = UIScreen.main.scale
      UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
      
      let textFontAttributes = [
      NSAttributedString.Key.font: textFont,
      NSAttributedString.Key.foregroundColor: textColor,
      ] as [NSAttributedString.Key : Any]
      image.draw(in: CGRect(origin: CGPoint.zero, size: image.size))
      
      text.draw(in: frame, withAttributes: textFontAttributes)
      
      let newImage = UIGraphicsGetImageFromCurrentImageContext()
      UIGraphicsEndImageContext()
      
      return newImage!
   }
   @objc func startScrolling() {
      print(originX)
      guard let images = images else { return }
      if originX == images.count {
         originX = 0
         numberOfIterations += 1
      }
      
      if numberOfIterations > 2 {
         timer?.invalidate()
         timer = nil
         numberOfIterations = 0
      }
      
      let x = CGFloat(originX) * frame.size.width
      setContentOffset(CGPoint(x: x, y: 0), animated: true)
      originX += 1
   }
}

Thanks upfront


Solution

  • Did you tried to remove the subviews, something like

        homePageView.subviews.forEach { (view) in
        //taking appropriate action to whichever view you want
            view.removeFromSuperview()//for removing views
        }