Home » Swift » App crash on sendEvent method

App crash on sendEvent method

Posted by: admin January 4, 2018 Leave a comment

Questions:

When I rotate the app twice after selecting a few items, it crashes. I have overridden the sendEvent method and that’s where the debugger stops. When I try to print the event type, it shows me something weird (I think it’s a memory location that doesn’t exist):

(lldb) print event.type
(UIEventType) $R10 = <invalid> (0xff)

Somehow I think this is related to how I handle the rotation. I have a master-detail style application, that uses a different type of navigation for pad-landscape, pad-portrait and phone. I have created a class named NavigationFlowController which handles all navigational events and sets up the views accordingly. On rotation, it breaks up the view trees and recomposes them with the correct navigation

func changeViewHierarchyForDevideAndOrientation(newOrientation:UIInterfaceOrientation? = nil){
    print("MA - Calling master layout method")

    UIApplication.myDelegate().window?.frame = UIScreen.mainScreen().bounds

    let idiom = UIDevice.currentDevice().userInterfaceIdiom
    var orientation:UIInterfaceOrientation!
    if let no = newOrientation{
        orientation = no
    }else{
        orientation = UIApplication.sharedApplication().statusBarOrientation
    }

    print("MA - Breaking up view tree...")

    breakupFormerViewTree([sidebarViewController, listViewController, detailViewController, loginViewController])

    print("MA - Start init navbackbone")

    initNavBackboneControllers()

    guard let _ = UIApplication.myDelegate().currentUser else {
        if idiom == UIUserInterfaceIdiom.Phone{
            currentState = AppState.PHONE
        }else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
            currentState = AppState.PAD_LANDSCAPE
        }else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
            currentState = AppState.PAD_PORTRAIT
        }

        print("MA - Current user is nil - resetting")

        mainViewController.addChildViewController(loginViewController)

        return
    }

    if idiom == UIUserInterfaceIdiom.Phone{
        currentState = AppState.PHONE

        leftNavigationController?.viewControllers = [listViewController]

        slideViewController?.rearViewController = sidebarViewController
        slideViewController?.frontViewController = leftNavigationController

        slideViewController?.rearViewRevealWidth = 267;

        mainViewController.addChildViewController(slideViewController!)
    }else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsLandscape(orientation){
        currentState = AppState.PAD_LANDSCAPE

        leftNavigationController!.viewControllers = [sidebarViewController, listViewController]
        rightNavigationController!.viewControllers = [detailViewController]
        detailViewController.navigationItem.leftBarButtonItems = []
        detailViewController.initLayout()

        print("MA - Init split view controller with VCs")

        splitViewController!.viewControllers = [leftNavigationController!, rightNavigationController!]

        mainViewController.addChildViewController(splitViewController!)

    }else if idiom == UIUserInterfaceIdiom.Pad && UIInterfaceOrientationIsPortrait(orientation){
        currentState = AppState.PAD_PORTRAIT

        leftNavigationController!.pushViewController(sidebarViewController, animated: false)
        leftNavigationController!.pushViewController(listViewController, animated: false)

        rightNavigationController!.pushViewController(detailViewController, animated: false)
        rightNavigationController?.setNavigationBarHidden(false, animated: false)

        slideViewController!.rearViewController = leftNavigationController
        slideViewController!.frontViewController = rightNavigationController

        detailViewController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "< Documenten", style: UIBarButtonItemStyle.Bordered, target: slideViewController, action: "revealToggle:")
        detailViewController.initLayout()
        slideViewController!.rearViewRevealWidth = 350;

        mainViewController.addChildViewController(slideViewController!)
    }

}

func breakupFormerViewTree(vcs:[UIViewController?]){
    for vc in vcs{
        if let vcUnwrapped = vc, _ = vcUnwrapped.parentViewController {
            vcUnwrapped.removeFromParentViewController()
            vcUnwrapped.view.removeFromSuperview()
        }
    }
}

func initNavBackboneControllers(){
    leftNavigationController = UINavigationController()
    leftNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
    leftNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
    leftNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
    leftNavigationController?.navigationBar.translucent = false

    rightNavigationController = UINavigationController()
    rightNavigationController?.navigationBar.barTintColor = UIColor(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
    rightNavigationController?.navigationBar.tintColor = UIColor.whiteColor()
    rightNavigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
    rightNavigationController?.navigationBar.translucent = false

    slideViewController = SWRevealViewController()
    slideViewController?.rearViewRevealOverdraw = 0;
    slideViewController?.bounceBackOnOverdraw = false;
    slideViewController?.stableDragOnOverdraw = true;
    slideViewController?.delegate = self

    if UIDevice.currentDevice().userInterfaceIdiom == UIUserInterfaceIdiom.Pad{
        splitViewController = UISplitViewController()
    }
}

EDIT (in response to Justin’s questions):

1) I’ve experienced the crash on all iOS8 iPad simulators.

2) From a fresh start, if I select like 6-7 items and then I rotate twice, it crashes. But I can also select an item, rotate a few times, select some more and keep rotating and at some point it will crash.

3) When an item is selected, the following code is executed:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let document = getInfoForSection(indexPath.section).documents[indexPath.item]
    if document.canOpen{
        openDocument(document)

        DataManager.sharedInstance.getDocument(document.uri, after: {
            (document:Document?) -> () in
            if let documentUnwrapped = document{
                let detailVC = NavigationFlowController.sharedInstance.detailViewController;
                if detailVC.document?.uri == documentUnwrapped.uri{
                    NavigationFlowController.sharedInstance.detailViewController.documentUpdated(documentUnwrapped)
                }
            }
        })
    }
}

And then in the detail view controller:

func initLayout(){
    if viewForCard == nil{
        // views not yet initialized, happens when initLayout if called from the document setter before this view has been loaded
        // just return, the layouting will be done on viewDidLoad with the correct document instead
        return
    }

    self.navigationItem.rightBarButtonItems = []

    if document == nil{
        // Removed code that handles no document selected
        ...
        return
    }

    heightForCard.constant = NavigationFlowController.sharedInstance.currentState == AppState.PHONE ? CARD_HEIGHT_PHONE : CARD_HEIGHT_TABLET

    viewForCard.hidden = false
    removeAllSubviews(viewForCard)
    removeAllSubviews(viewForDetails)

    viewForDetails.translatesAutoresizingMaskIntoConstraints = false

    self.metaVC?.document = document
    //self.documentVC?.document = document
    self.navigationItem.rightBarButtonItems = []

    downloadDocumentIfNeeded()

    if NavigationFlowController.sharedInstance.currentState == AppState.PAD_LANDSCAPE || NavigationFlowController.sharedInstance.currentState == AppState.PAD_PORTRAIT{
        self.viewForDetails.backgroundColor = document?.senderStyling?.color

        addChildViewController(self.metaVC!)
        addChildViewController(self.documentVC!)

        let metaView = self.metaVC!.view
        let documentView:UIView = self.documentVC!.view

        viewForDetails.addSubview(metaView)
        viewForDetails.addSubview(documentView)

        // whole lot of layouting code removed
        ...            

        let doubleTap = UITapGestureRecognizer(target: self, action: "toggleZoom")
        documentVC!.view.addGestureRecognizer(doubleTap)

    }else{
        // Phone version code removed
        ...
    }   
}

EDIT2:

func downloadDocumentIfNeeded(){
    var tmpPath:NSURL?
    if let url = document?.contentUrl{
        let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]

        if let docName = self.document?.name,
            safeName = disallowedCharacters?.stringByReplacingMatchesInString(docName, options: [], range: NSMakeRange(0, docName.characters.count), withTemplate: "-"){
                tmpPath = directoryURL.URLByAppendingPathComponent("\(safeName)_\(DetailViewController.dateFormatter.stringFromDate(self.document!.creationDate!)).pdf")

        }


        if let urlString = tmpPath?.path{
            if NSFileManager.defaultManager().fileExistsAtPath(urlString) {
                // File is there, load it
                loadDocumentInWebview(tmpPath!)
            }else{
                // Download file
                let destination: (NSURL, NSHTTPURLResponse) -> (NSURL) = {
                    (temporaryURL, response) in

                    if let path = tmpPath{
                        return path
                    }

                    return temporaryURL
                }

                download(.GET, URLString: url, destination: destination).response {
                    (request, response, data, error) in

                    if error != nil && error?.code != 516{
                        ToastView.showToastInParentView(self.view, withText: "An error has occurred while loading the document", withDuaration: 10)
                    }else if let pathUnwrapped = tmpPath {
                        self.loadDocumentInWebview(pathUnwrapped)
                    }
                }
            }
        }
    }
}

func loadDocumentInWebview(path:NSURL){
    if self.navigationItem.rightBarButtonItems == nil{
        self.navigationItem.rightBarButtonItems = []
    }

    self.documentVC?.finalPath = path

    let shareItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Action, target: self, action: "share")
    shareItem.tag = SHARE_ITEM_TAG

    addNavItem(shareItem)

}

func addNavItem(navItem:UIBarButtonItem){
    var addIt = true
    for item in self.navigationItem.rightBarButtonItems!{
        if item.tag == navItem.tag{
            addIt = false
        }
    }

    if addIt{
        self.navigationItem.rightBarButtonItems?.append(navItem)
        self.navigationItem.rightBarButtonItems!.sortInPlace({ $0.tag > $1.tag })
    }
}

EDIT3: I’ve overridden the sendEvent method to track whether or not a user is touching the app or not, but even if I take out this code, it still crashes, and the debugger then breaks on UIApplicationMain.

override func sendEvent(event: UIEvent)
{        
    super.sendEvent(event)

    if event.type == UIEventType.Touches{
        if let touches = event.allTouches(){
            for item in touches{
                if let touch = item as? UITouch{
                    if touch.phase == UITouchPhase.Began{
                        touchCounter++
                    }else if touch.phase == UITouchPhase.Ended || touch.phase == UITouchPhase.Cancelled{
                        touchCounter--
                    }

                    if touchCounter == 0{
                        receiver?.notTouching()
                    }
                }
            }
        }
    }
}
Answers:

Tough one, a bit more insight in the events upto this bug might be helpful.

  1. Does it happen on every device (if not, which devices gives you troubles)
  2. It happens after “vigorously selecting” items. Did your device change orientation before that. Does it also happen before you once rotate?
  3. What do you do in code when you “select an item”.

Other then that, I’d start to get the flow of removing your child ViewControllers in breakupFormerViewTree() right. Based on the Apple Docs you want to tell the child it’s being removed, before removing the view and then finally removing the child from the Parent ViewController

https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html

Here it actually says you want to call willMoveToParentViewController(nil) before doing the removing. It doesn’t say what happens if you don’t, but I can imagine the OS doing some lifecycle management there, preventing it from sending corrupt events at a later point.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/willMoveToParentViewController:

EDIT (After extra was code posted)

I don’t see anything else in your code that might cause it to crash. It does look like a memory-error as you stated, but no idea where it’s coming from. Try turning on Zombie objects and Guard Malloc (Scheme > Run > Diagnostics) and maybe you can get a bit more info on what’s causing it.

Other then that, I’d just comment out loads of your implementation, swap Subclasses with empty ViewControllers until it doesn’t happen again. You should be able to pinpoint what part of your implementation is involved in creating this event. Once you do that, well, pinpoint more and evaluate every single line of code in that implementation.