Facebook style sidemenu from scratch with Rubymotion

14 Nov 2012, by Michael Cindrić

In the process of developing our little internal game using RubyMotion we ran into an issue using the pod MFSideMenu and the RubyMotion framework so we decided to write our own and here is what we came up with.

We created a Menu Manager class to handle the instance of the menu and its actions such as the slide in and out. It also has the nice shadow affect on it as well

class MenuManager
  attr_accessor :navigationController, :menu, :visible

  SHADOW_WIDTH = 10.0 

  @@instance = nil

  #Used to simply maintain state of sidemenu
  @@visible = false

  def self.instance
    return @@instance unless @@instance.nil?

    @navigationController = UINavigationController.alloc.initWithRootViewController(DashboardController.build)
    @navigationController.navigationBar.tintColor = UIColor.colorWithRed 168.0/255, green: 15.0/255, blue: 17.0/255, alpha: 1.0
    @menu = SideMenuController.build

    @@instance = MenuManager.new(@navigationController, @menu)

    @@instance
  end

  def initialize(navigationController, menuController)
    @navigationController = navigationController
    @menu = menuController
  end

  def setupMenuView
    self.navigationController.view.superview.insertSubview(self.menu.view, belowSubview: self.navigationController.view)

    pathRect = self.navigationController.view.bounds
    pathRect.size.width = SHADOW_WIDTH
    self.navigationController.view.layer.shadowPath = UIBezierPath.bezierPathWithRect(pathRect).CGPath

    self.navigationController.view.layer.shadowOpacity = 0.75
    self.navigationController.view.layer.shadowRadius = SHADOW_WIDTH
    self.navigationController.view.layer.shadowColor = UIColor.blackColor.CGColor
  end

  def toggleMenuState
    destination = self.navigationController.view.frame

    if destination.origin.x > 0
      destination.origin.x = 0 
      @visible = false 
    else      
      destination.origin.x += 254.5
      @visible = true 
    end

    UIView.animateWithDuration 0.25,
                               animations: -> { self.navigationController.view.frame = destination}

    navigationController.visibleViewController.view.userInteractionEnabled = !(destination.origin.x > 0)
  end

  def self.menuButton(target, action)
    menuBarButtonItem = UIBarButtonItem.alloc.initWithImage(UIImage.imageNamed("navBar/menu-icon.png"), 
                                                            style: UIBarButtonItemStyleBordered, 
                                                            target: target,
                                                            action: "#{action}")
    return menuBarButtonItem
  end

end

You will also need a UITableViewController which is the actual view seen by the user. No in our case we have hard coded this controller but if we were to make this into a gem we would extract that as params passed in of course but you get the idea.

class SideMenuController < UITableViewController

  def self.build
    @controller ||= alloc.initWithNibName(nil, bundle: nil)
  end

  def viewDidLoad
    super

    @controllers = [ProfileController.build, 
                    DashboardController.build,
                    LeaderboardController.build,
                    GuideController.build,
                    OptionsController.build,
                    StoreController.build]

    @labels = ["Profile", "Dashboard", "Leaderboard", "How to Play", "Options", "Store"]


    tableView.separatorStyle = UITableViewCellSeparatorStyleNone
    tableView.backgroundColor = UIColor.clearColor
    tableView.backgroundView = UIImageView.alloc.initWithImage(UIImage.imageNamed("menu/bg.png"))

    #Need to set this dynamically to handle the first cell being larger then the rest
    tableView.rowHeight = 50
  end

  def tableView(tableView, heightForRowAtIndexPath: indexPath)
    if (indexPath.row == 0)
      height = 80
    else
      height = 50
    end

    return height
  end

  def tableView(tableView, cellForRowAtIndexPath: indexPath)
    @reuseIdentifier ||= "MenuCell"

    cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier) ||
      UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier: @reuseIdentifier)

    if (indexPath.row == 0)
      height = 50
    else
      height = 10
    end

    bg = UIImageView.alloc.initWithFrame(CGRectZero)
    # cell.setBackgroundView.image = UIImage.imageNamed("awardbg.png")

    #Stop blue appearing when selecting cell
    # cell.setSelectionStyle(UITableViewCellSelectionStyleNone)

    label = LabelFactory.makeLabel([[80, height], [150.0, 30.0]], 
                                    text: @labels[indexPath.row], 
                                    font: DesignFactory.fontSaturator(20.0),
                                    align: UITextAlignmentLeft,
                                    color: UIColor.whiteColor)

    cell.contentView.addSubview label

    return cell
  end

  def tableView(tableView, numberOfSectionsInTableView: sections)
    return 1
  end

  def tableView(tableView, numberOfRowsInSection: section)
    return @controllers.length
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    menuManager = MenuManager.instance

    #Return only the controller we want to display but needs to be in array
    menuManager.navigationController.viewControllers = [@controllers[indexPath.row]]    
    menuManager.toggleMenuState
  end

end

Now all thats left to do is set it up in the app delegate like so


    menuManager = MenuManager.instance
    @window.rootViewController = menuManager.navigationController

    #Need to call this here for MenuManager to know about the UIView    
    @window.makeKeyAndVisible    

    #Need to call this here for now till we start passing through the @window to MenuManager
    menuManager.setupMenuView

You might notice we have a call to a class called LabelFactory in there as well. This is just a class we created to help with the creation of labels and their style for our app. You can just replace this with a normal UILabel call.

One of our readers pointed out l never showed how to call the method from the UIController so here is an example call that you would make in the ViewDidLoad method

self.navigationItem.leftBarButtonItem = MenuManager.menuButton(self, "showMenu")

Cookies help us deliver our services. By using our services, you agree to our use of cookies.