Home > Back-end >  Swift calling ChildVC sometimes causes fatal error
Swift calling ChildVC sometimes causes fatal error

Time:02-02

I have been trying to debug this for several days now but I can't seem to understand how it happens. I might be missing some information on how child view controllers are called.

I have a map that opens a child view controller on longpress. This works most of the time. Sometimes though, the app crashes. The console tells me:

Fatal error: Attempted to read an unowned reference but object 0x283f3cf80 was already deallocated

Note that this does not happen most of the time. It is rare and sometimes I need to trigger the long-press many times until it comes up. This is why I cannot use breakpoints. To help me debug, I have added many prints starting from "CHECK 1" to "CHECK 16". These print in the correct order when everything works well. When it breaks, it breaks between "CHECK 14" and "CHECK 15".

I use this code to call my ChildVC:

let slideVC = TripOverviewVC(customer: myCustomer, route: myRoute, panelController: panelController!, annotationManager: annotationManager!, mapview: mapView!)
slideVC.view.roundCorners([.topLeft, .topRight], radius: 22)
slideVC.view.layer.zPosition = 15
print("CHECK 8")
add(slideVC)
print("CHECK 13")

let height = view.frame.height
let width = view.frame.width
slideVC.view.frame = CGRectMake(0, self.view.frame.maxY, width, height)
print("CHECK 14")

And this is my add(_ child) function:

func add(_ child: UIViewController) {
    addChild(child)
    print("CHECK 9")
    view.addSubview(child.view)
    print("CHECK 11")
    child.didMove(toParent: self)
    print("CHECK 12")
}

On my child VC, the viewWillAppear prints "CHECK 10" which is called correctly. However, after the child has been added using my add function, I guess that viewDidAppear should be called. But it never does. This is my viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    print("CHECK 15")
    UIView.animate(withDuration: 0.3) { [weak self] in
        let frame = self?.view.frame
        let yComponent = UIScreen.main.bounds.height - self!.view.frame.height*0.5
        self?.view.frame = CGRectMake(0, yComponent, frame!.width, frame!.height)
    }
    tmpOrigin = self.view.frame.origin
    print("CHECK 16")
}

This means that something breaks between adding the child as a subview, and before it is displayed on the screen, which is exactly what I can see on my app. What I don't understand is: What is being called between the two steps? Have I misunderstood the view lifecycle?

CodePudding user response:

OK, this may help or may not but it is a small change so you could potentially try it out.

As per the Apple developer documentation (available here), under the section

Adding a Child View Controller to Your Content

it is indicated that the frame of the child view controller's view should be set during addition of it to the parent's view-hierarchy.

In your code above, it seems that the frame is being set after the call to didMove(toParent:), which is not exactly in-line with what Apple says that we should do.

Based on my experience, the problem you are facing seems to be something that could occur due to this minor difference.

You could potentially change func add(_ child: UIViewController) to func add(_ child: UIViewController, with frame: CGRect), pass the frame you set before print("CHECK 14"), and then set it after print("CHECK 9"), just like Apple states.

func add(_ child: UIViewController, with frame: CGRect) {
    addChild(child)
    print("CHECK 9")
    child.view.frame = frame
    print("CHECK 10")
    view.addSubview(child.view)
    print("CHECK 11")
    child.didMove(toParent: self)
    print("CHECK 12")
}

And then your initial code would change to

let slideVC = TripOverviewVC(customer: myCustomer, route: myRoute, panelController: panelController!, annotationManager: annotationManager!, mapview: mapView!)
slideVC.view.roundCorners([.topLeft, .topRight], radius: 22)
slideVC.view.layer.zPosition = 15
print("CHECK 8")

let height = view.frame.height
let width = view.frame.width
let frame = CGRectMake(0, self.view.frame.maxY, width, height)
add(slideVC, with: frame)
print("CHECK 13")

// print("CHECK 14") (this is not useful now)

If this helps, please do consider marking this as the answer.

CodePudding user response:

Here the force unwrapping is the problem which may lead to crash when the self is released before the execution of the animation which is delayed by 3 sec

let yComponent = UIScreen.main.bounds.height - self!.view.frame.height*0.5 
self?.view.frame = CGRectMake(0, yComponent, frame!.width, frame!.height)

Instead it can be updated as

    UIView.animate(withDuration: 0.3) { [weak self] in 
    guard let this = self else { return }
        let frame = this.view.frame
        let yComponent = UIScreen.main.bounds.height - this.view.frame.height *0.5
        this.view.frame = CGRectMake(0, yComponent, frame.width, frame.height)
    }
  • Related