Facebook style sidemenu from scratch with Rubymotion
14 Nov 2012In 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")