Search code examples
iosswiftuiviewuibuttonaddtarget

Button click crashes the whole application


I am currently building an application's UI without using IB or storyboard. I have the following hierarchy: 1. ViewController: which is the main view controller. 2. DrawerViewController: which is the view controller for the lower drawer menu. The main view controller adds as a subview the drawerViewController's view. 3. Add Button: this is a UIButton created and added programatically to the drawerViewController's view.

The problem is if I clicked on the add button, the application crashes, giving absolutely no error messages in the output, except (lldb).

I have tried the following:

  1. Change the selector name and the method name.
  2. Checked the name of the selector method.
  3. Added and removed the parameter of the selector method.
  4. Changed the name of the button.
  5. Added the button inside another view.

There's no way to trace the bug using breakpoints, because the application is compiling just fine, and the app crashes only if you click on the button.

Here's a link to a testing project I created to show the problem:

GitHub

ViewController:

class ViewController: UIViewController //, DrawerDelegate
{

//Lower Drawer.
var drawerView: DrawerView!

var drawerViewHidden: Bool = false
var addButton: UIButton!
var viewHeight: CGFloat! = 0
var viewWidth : CGFloat! = 0
var shownDrawerViewY: CGFloat!
var hiddenDrawerViewY: CGFloat!

init()
{
    super.init(nibName: nil, bundle: nil)
}

required init(coder aDecoder: NSCoder)
{
    super.init(coder: aDecoder)
}



override func viewDidLoad()
{
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    //Setup UI
    setUpUI()
}

override func viewWillAppear(animated: Bool)
{
    super.viewWillAppear(animated);


}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


//MARK:- UI initialization.
/**
Private function that setsup the UI.
*/
private func setUpUI()
{
    setUpDrawer()
}

//MARK: Drawer setup.

/**
A private function that programatically creataes a drawer and add it to the view.
*/
private func setUpDrawer()
{
    var controller = DrawerViewController(width: self.view.frame.width, height: self.view.frame.height)

    self.view.addSubview(controller.view)
    //Hide
    // toggleDrawer()
}



//MARK:- Delegates.

//MARK: DrawerDelegate methods.
func addButtonClicked(sender: UIButton)
{
    //  toggleDrawer()
}   
}

DrawerViewController

class DrawerViewController: UIViewController
{
    //Drawer variables.
    var drawerViewHidden: Bool = false
    var addPinDrawer: AddPinDrawer!



    var viewHeight: CGFloat! = 0
    var viewWidth : CGFloat! = 0
    var shownDrawerViewY: CGFloat?
    var hiddenDrawerViewY: CGFloat?

    var addButton: UIButton?

    init(width: CGFloat, height: CGFloat)
    {
        super.init(nibName: nil, bundle: nil)

        viewWidth  = width
        viewHeight = height

        setUpUI()
    }

    override func viewDidLoad()
    {
        super.viewDidLoad()

        // Do any additional setup after loading the view.

    }

    override func viewWillAppear(animated: Bool)
    {
        super.viewWillAppear(animated)


    }

    override func didReceiveMemoryWarning()
    {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    required override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
    {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init(coder aDecoder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }


    //MARK:- UI setup
     func setUpUI()
    {
        setUpDimensions()
        setUpDrawerElements()
//        toggleDrawer()
    }

     func setUpDimensions()
    {
        //Determine the y in the case drawer is shown
        shownDrawerViewY  = viewHeight * 2.0/8.0

        //Determine the height of the drawer.
        let drawerHeight = viewHeight * 6.0/8.0

        //Determine the y in the case drawer is hidden
        hiddenDrawerViewY = viewHeight * 7.4/8.0

        //Create the frame, starting with the drawer shown.
        let frame = CGRectMake(0, shownDrawerViewY!, viewWidth, drawerHeight)
        //Create a new Drawer View.
        self.view = UIView(frame: frame)

        setUpAddButton(frame)



        //Setup the background image of the drawer.
        self.view.backgroundColor = UIColor(patternImage: UIImage(named: Constants.drawerBackgroundImage)!)
    }

    func setUpDrawerElements()
    {
        //Setup the button.
        setUpAddPinDrawer()
    }

    func setUpAddPinDrawer()
    {
        addPinDrawer = AddPinDrawer(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height))
//        self.view.addSubview(addPinDrawer)
    }



    //MARK: Handling drawer toggles
    func toggleDrawer()
    {
        //Toggle the variable.
        drawerViewHidden = !drawerViewHidden

        //If the drawer must be hidden.
        if drawerViewHidden
        {
            hideDrawer()
        }

        //If the drawer must be shown
        else
        {
            showDrawer()
        }
    }

    func hideDrawer()
    {
        //Hide the drawer
        UIView.animateWithDuration(0.6, animations: { () -> Void in
            self.view.frame.origin.y = self.hiddenDrawerViewY!
        })
      //  drawerView.hideDrawer()
    }

    func showDrawer()
    {
        UIView.animateWithDuration(0.6, animations: { () -> Void in
            self.view.frame.origin.y = self.shownDrawerViewY!
        })
      //  drawerView.showDrawer()
    }


    func setUpAddButton(frame: CGRect!)
    {
        //Determine the button dimensions.
        let width:CGFloat = 75.0//1.0/10.0 * viewHeight
        let x = frame.width/2.0 - width/2.0

        //Button background image.
        let background = UIImage(named: Constants.addButtonBackgroundImage)!

        //Create the button.
        addButton = UIButton(frame: CGRectMake(x, -width/2.0, width, width)) as UIButton
//        addButton!.setImage(background, forState: UIControlState.Normal)

        addButton?.backgroundColor = UIColor.yellowColor()

        //Add the event handler.
        addButton!.addTarget(self, action: "buttonAdd:", forControlEvents: .TouchUpInside)


        //Set it rotated.
//        self.addButton!.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))


        //Add the button to the subview.
        self.view.addSubview(addButton!)

//        println(addButton!.targetForAction("test", withSender: addButton)!)
    }

    func buttonAdd(sender: UIButton)
    {
        println("Helloooo asdf")
    }
}

Solution

  • I have solved the problem. There's a very little mistake in the initiation of the DrawerViewController, In the main view controller, I am creating a variable called DrawerViewController and initiating its view. This variable will be deallocated once the method is done, which means all events will not be handled. Causing the app to crash since there's no method to handle the button's events.

    The solution:

    Make the DrawerViewController an instance variable and initialise it in the main view controller.