iosswiftlayoutconstraintsakswiftslidemenu

Blank Menu Button with Swift Slide Menu


I have a weird issue when creating the Slide out menu in swift,

slide menu blank button

I'm building the menu using AKSwiftSlideMenu as a reference,

This only happens on the ViewControllers that have a UITableViewDataSource, UITableViewDelegate.

If I go to the other view controllers without one, the menu shows up fine.

Below is my code for BaseViewController

class BaseViewController: UIViewController, SlideMenuDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
}

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

func slideMenuItemSelectedAtIndex(_ index: Int32) {
    let topViewController : UIViewController = self.navigationController!.topViewController!
    print("View Controller is : \(topViewController) \n", terminator: "")
    switch(index){
    case 0:
        print("Locations\n", terminator: "")

        self.openViewControllerBasedOnIdentifier("Locations")

        break
    case 1:
        print("Offers\n", terminator: "")

        self.openViewControllerBasedOnIdentifier("Offers")

        break
    case 2:
        print("Feedback\n", terminator: "")

        self.openViewControllerBasedOnIdentifier("Feedback")

        break
    case 3:
        print("About\n", terminator: "")

        self.openViewControllerBasedOnIdentifier("About")

        break
    case 4:
        for key in UserDefaults.standard.dictionaryRepresentation().keys {
            UserDefaults.standard.removeObject(forKey: key)
        }
        //fb logout
        if(FBSDKAccessToken.current() != nil) {
            FBSDKAccessToken.setCurrent(nil)
            FBSDKProfile.setCurrent(nil)
        }

        self.openViewControllerBasedOnIdentifier("SocialLogin")
    default:
        print("default\n", terminator: "")
    }
}

func openViewControllerBasedOnIdentifier(_ strIdentifier:String){
    let destViewController : UIViewController = self.storyboard!.instantiateViewController(withIdentifier: strIdentifier)

    let topViewController : UIViewController = self.navigationController!.topViewController!

    if (topViewController.restorationIdentifier! == destViewController.restorationIdentifier!){
        print("Same VC")
    } else {
        self.navigationController!.pushViewController(destViewController, animated: true)
    }
}

func addSlideMenuButton(){
    let btnShowMenu = UIButton(type: UIButtonType.system)
    btnShowMenu.setImage(self.defaultMenuImage(), for: UIControlState())
    btnShowMenu.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
    btnShowMenu.addTarget(self, action: #selector(BaseViewController.onSlideMenuButtonPressed(_:)), for: UIControlEvents.touchUpInside)
    let customBarItem = UIBarButtonItem(customView: btnShowMenu)
    self.navigationItem.leftBarButtonItem = customBarItem;
}

func defaultMenuImage() -> UIImage {
    var defaultMenuImage = UIImage()

    UIGraphicsBeginImageContextWithOptions(CGSize(width: 30, height: 22), false, 0.0)

    UIColor.black.setFill()
    UIBezierPath(rect: CGRect(x: 0, y: 3, width: 30, height: 1)).fill()
    UIBezierPath(rect: CGRect(x: 0, y: 10, width: 30, height: 1)).fill()
    UIBezierPath(rect: CGRect(x: 0, y: 17, width: 30, height: 1)).fill()

    UIColor.white.setFill()
    UIBezierPath(rect: CGRect(x: 0, y: 4, width: 30, height: 1)).fill()
    UIBezierPath(rect: CGRect(x: 0, y: 11,  width: 30, height: 1)).fill()
    UIBezierPath(rect: CGRect(x: 0, y: 18, width: 30, height: 1)).fill()

    defaultMenuImage = UIGraphicsGetImageFromCurrentImageContext()!

    UIGraphicsEndImageContext()

    return defaultMenuImage;
}

func onSlideMenuButtonPressed(_ sender : UIButton){
    if (sender.tag == 10)
    {
        // To Hide Menu If it already there
        self.slideMenuItemSelectedAtIndex(-1);

        sender.tag = 0;

        let viewMenuBack : UIView = view.subviews.last!

        UIView.animate(withDuration: 0.3, animations: { () -> Void in
            var frameMenu : CGRect = viewMenuBack.frame
            frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
            viewMenuBack.frame = frameMenu
            viewMenuBack.layoutIfNeeded()
            viewMenuBack.backgroundColor = UIColor.clear
            }, completion: { (finished) -> Void in
                viewMenuBack.removeFromSuperview()
        })

        return
    }

    sender.isEnabled = false
    sender.tag = 10

    let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "MenuViewController") as! MenuViewController
    menuVC.btnMenu = sender
    menuVC.delegate = self
    self.view.addSubview(menuVC.view)
    self.addChildViewController(menuVC)
    menuVC.view.layoutIfNeeded()


    menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height);

    UIView.animate(withDuration: 0.3, animations: { () -> Void in
        menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height);
        sender.isEnabled = true
        }, completion:nil)
}
}

MenuViewController

protocol SlideMenuDelegate {
func slideMenuItemSelectedAtIndex(_ index : Int32)
}

class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

/**
*  Array to display menu options
*/
@IBOutlet var tblMenuOptions : UITableView!

/**
*  Transparent button to hide menu
*/
@IBOutlet var btnCloseMenuOverlay : UIButton!

/**
*  Array containing menu options
*/
var arrayMenuOptions = [Dictionary<String,String>]()

/**
*  Menu button which was tapped to display the menu
*/
var btnMenu : UIButton!

/**
*  Delegate of the MenuVC
*/
var delegate : SlideMenuDelegate?

override func viewDidLoad() {
    super.viewDidLoad()
    tblMenuOptions.tableFooterView = UIView()
    // Do any additional setup after loading the view.
}

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

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    updateArrayMenuOptions()
}

func updateArrayMenuOptions(){
    arrayMenuOptions.append(["title":"Locations", "icon":"LocationIcon"])
    arrayMenuOptions.append(["title":"Offers", "icon":"OffersIcon"])
    arrayMenuOptions.append(["title":"Feedback", "icon":"FeedbackIcon"])
    arrayMenuOptions.append(["title":"About", "icon":"AboutIcon"])
    arrayMenuOptions.append(["title":"Logout", "icon":"LogoutIcon"])

    tblMenuOptions.reloadData()
}

@IBAction func onCloseMenuClick(_ button:UIButton!){
    btnMenu.tag = 0

    if (self.delegate != nil) {
        var index = Int32(button.tag)
        if(button == self.btnCloseMenuOverlay){
            index = -1
        }
        delegate?.slideMenuItemSelectedAtIndex(index)
    }

    UIView.animate(withDuration: 0.3, animations: { () -> Void in
        self.view.frame = CGRect(x: -UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width,height: UIScreen.main.bounds.size.height)
        self.view.layoutIfNeeded()
        self.view.backgroundColor = UIColor.clear
        }, completion: { (finished) -> Void in
            self.view.removeFromSuperview()
            self.removeFromParentViewController()
    })
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cellMenu")!

    cell.selectionStyle = UITableViewCellSelectionStyle.none
    cell.layoutMargins = UIEdgeInsets.zero
    cell.preservesSuperviewLayoutMargins = false
    cell.backgroundColor = UIColor.clear

    let lblTitle : UILabel = cell.contentView.viewWithTag(101) as! UILabel
    let imgIcon : UIImageView = cell.contentView.viewWithTag(100) as! UIImageView

    imgIcon.image = UIImage(named: arrayMenuOptions[indexPath.row]["icon"]!)
    lblTitle.text = arrayMenuOptions[indexPath.row]["title"]!

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let btn = UIButton(type: UIButtonType.custom)
    btn.tag = indexPath.row
    self.onCloseMenuClick(btn)
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return arrayMenuOptions.count
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 1;
}
}

a example of view controller where this happens

class LocationViewController: BaseViewController, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
var locItems:Array<LocItems>?
var locItemsWrapper:LocItemsWrapper?
var isLoadingLocItems = false


private var tb: UITableView?

override func viewDidLoad() {
    super.viewDidLoad()

    //set up tableview
    tb = UITableView()

    //Set button to open up menu
    self.addSlideMenuButton()

}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if (self.locItems == nil) {
        return 0
    }
    return self.locItems!.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "locCell", for: indexPath) as! LocItemCell

    if(self.locItems != nil && self.locItems!.count >= indexPath.row) {
        let locItem = self.locItems![indexPath.row]
        let rowsLoaded = self.locItems!.count
        if (!self.isLoadingLocItems && (indexPath.row >= (rowsLoaded - rowsToLoadFromBottom))) {
            let totalRows = self.locItemsWrapper!.count!
            let remainingLocItemsToLoad = totalRows - rowsLoaded
            if(remainingLocItemsToLoad > 0) {
                self.loadMoreLocItems()
            }
        }
    }

    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    if(self.locItems!.count >= indexPath.row) {
        selectedLocId = self.locItems![indexPath.row].id!
        selectedLocBg = self.locItems![indexPath.row].locationBackground!

        //TODO: Set up Switch on api call

        let parameters = [
            "locId": selectedLocId
        ]

    }

}

override func prepare(for segue: (UIStoryboardSegue!), sender: Any!) {
    if(segue.identifier == "showCongratOffer") {
        let svc = segue.destination as! CongratOfferViewController

        svc.offerId = selectedLocId
        svc.type = 1
    }
}

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if((indexPath.row % 2) == 0) {
        cell.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
    } else {
        cell.backgroundColor = UIColor.white
    }
}

I know it's a lot of code, I tried to cut out pieces in the example where it happens as much as possible while leaving the menu code complete,

I would really appreciate if someone could help me with this issue or point me in the right direction.

A picture of the tableview before I open the menu,

enter image description here

A picture of the storyboard, The top left controller can be ignored, from login (top middle), we go to locations (bottom right, bottom middle) where the menu exists, about (top right) will show the menu properly without the blank space, the menu controller is bottom left.

The menu still works, though the empty space shows up in controllers with a UITableViewDataSource, UITableViewDelegate.

enter image description here enter image description here

UPDATE: Looking at the view and how it gets built, I've found the menu height constraint is different. It's not getting set after menuVC.view.layoutIfNeeded() in BaseViewController.

print(self.childViewControllers[0].topLayoutGuide)

Empty Space Issue -> <_UILayoutGuide: 0x7fce6e7115e0; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x600000238700>>
Working -> <_UILayoutGuide: 0x7fce6e51ad30; frame = (0 0; 0 64); hidden = YES; layer = <CALayer: 0x60000023c9a0>>

How would you change just the height constraint on the view? I can get the read-only view constraints like so, self.childViewControllers[0].view.constraints


Solution

  • I see the issue now. What you need to do is copy the cell from your TableView, delete the original cell, make sure the TableView is completely empty, and paste the Cell back in. You can do this manually as well by making a new cell entirely. BTW, the issue is that there is an empty space above your cell which you need to remove from your TableView.