Is there a way to implement Slide Sidebar Menu (Like Facebook app) in IOS Swift without any third party library? I look for solutions but I only founded this feature implemented in Objective-C.
问题:
回答1:
I believe you can start form UISplitViewController which was drastically updated in iOS8. Watch sessions View Controller Advancements in iOS8 and Building Adaptive Apps with UIKit for details. They provide code example from second session (but not form first one :/). At this point it feels natural for me to make this kind of UI based on split view controller in iOS8.
Update: It looks like not all API they talking about are landed yet. In current beta4 mentioned in video condensesBarsOnSwipe is not presented, for example.
回答2:
Update: Please consider using my updated answer rather than this one. There's a lot of edge cases with the Scrollview/Container View approach that can be avoided by using Custom View Controller Transitions instead.
I looked all over the place for a Swift menu solution that didn't require a library. Ended up creating a tutorial of my own, which is fairly similar to Fengson's approach:
Here are some of the main points:
- Create a
scrollview
which will handle the menu sliding motion, along with the pan gesture - Put two
container
views side by side, inside thescrollview
. Containers allow you to embed top level controllers. - On the
scrollview
, setpaging enabled
but disablebounces
. This will coerce the slideout into an open or closed state embed
aUITableViewController
in the left containerembed
aUITabBarController
in the right container- On the right
container
, add a runtime attributelayer.shadowOpacity
of 0.8. This gives you a free shadow separator without any code. - Add a menu button. You can use
NSNotificationCenter
to communicate with thescrollview
- For the secret ingredient: use
scrollView.setContentOffset.x
to take care of the actual opening and closing of the menu
Here's a working example project of a tab bar application with a left slideout menu, including screenshots and instructions.
https://github.com/ThornTechPublic/LeftSlideoutMenu
Along with a more generic explanation of how it works:
http://www.thorntech.com/2015/06/want-to-implement-a-slideout-menu-in-your-swift-app-heres-how/
回答3:
Slide side bar menus for iOS7 and iOS8, Swift coded.
If you want it at NavigationController level (it means, for al View controllers behind it):
https://github.com/evnaz/ENSwiftSideMenu
If you want it just in one ViewController:
Video: https://www.youtube.com/watch?v=qaLiZgUK2T0
Source-code: http://goo.gl/ULWxJh
In this case, for iOS7 compatibility, just add this "if" condition where the "Add blur view" comment is placed, like this:
if (NSClassFromString("UIVisualEffectView") != nil) {
// Add blur view
let blurView:UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.Light))
blurView.frame = sideBarContainerView.bounds
sideBarContainerView.addSubview(blurView)
}
回答4:
I think using Custom View Controller Transitions is a solid approach for making slideout menus:
- You can customize the animation.
- The transition is interactive, so you can drag to progress forward and backward. The transition always finishes or rolls back, and never gets stuck in-between states.
- You use Pan Gestures and Screen Edge Pan Gestures to drive the interaction. This means you can place them strategically to minimize conflict with horizontally gestured content.
- You don't have to resort to Container Views. This means your app architecture is flatter, and you can use protocol-delegate instead of NSNotifications.
- There's only one ViewController active at a time. Snapshots are used to give the illusion that there's a second VC on the screen.
Custom View Controller Transitions are difficult to learn at first (they were for me, at least). I wrote a blog post on how to create an interactive slideout menu, and tried my best to make it as easy to understand as possible.
You can also jump straight into the code on GitHub.
At a very high level, this is how it works:
- You open the slideout menu as a modal.
- You create custom animations for the present and dismiss transitions using the
UIViewControllerAnimatedTransitioning
protocol. You use a snapshot to represent the main view controller during animations. - You create Pan Gesture Recognizers to present/dismiss the modal interactively.
- You wire the Pan Gesture events to a
UIPercentDrivenInteractiveTransition
object to keep the transition in sync with the user's movements. - The presenting controller adopts the
UIViewControllerTransitioningDelegate
protocol and wires up all the custom animations and interactive transitions.
I actually have a previous answer on this thread that uses a Scrollview / Container View. This approach is OK for a prototype, but runs into a lot of edge cases and bugs when making your app production ready. Spending every week responding to blog comments and fixing edge cases is what motivated me to write a second blog post on the subject.
回答5:
Here's another SideMenu library I made to add to the mix: https://github.com/jonkykong/SideMenu.
Simple side menu control for iOS in Swift inspired by Facebook. Right and Left sides. No coding required.
- It can be implemented in storyboard without a single line of code.
- Four standard animation styles to choose from (even parallax if you want to get weird).
- Highly customizable without needing to write tons of custom code.
- Supports continuous swiping between side menus on boths sides in a single gesture.
- Global menu configuration. Set-up once and be done for all screens.
- Menus can be presented and dismissed the same as any other View Controller since this control uses custom transitions.
回答6:
Here is a small example of how I do it, this drawer control has uiviewcontrollers for left, center, and right. This one works like the Slack IPad app where either one side or the other is open.
/*
To use simply instantiate SlidingDrawerController as your root view in your AppDelegate, or in the
StoryBoard.
Once SlidingDrawerController is instantiated, set the drawerSize of the SlidingDrawerController,
and its leftViewControllerIdentifier, centerViewControllerIdentifier, and
rightViewControllerIdentifier to the Storyboard Identifier of the UIViewController
you want in the different locations.
*/
class SlidingDrawerController: UIViewController {
var drawerSize:CGFloat = 4.0
var leftViewControllerIdentifier:String = "leftViewController"
var centerViewControllerIdentifier:String = "centerViewController"
var rightViewControllerIdentifier:String = "rightViewController"
enum Drawers {
case left
case right
}
private var _leftViewController:UIViewController?
var leftViewController:UIViewController {
get{
if let vc = _leftViewController {
return vc;
}
return UIViewController();
}
}
private var _centerViewController:UIViewController?
var centerViewController:UIViewController {
get{
if let vc = _centerViewController {
return vc;
}
return UIViewController();
}
}
private var _rightViewController:UIViewController?
var rightViewController:UIViewController {
get{
if let vc = _rightViewController {
return vc;
}
return UIViewController();
}
}
static let SlidingDrawerOpenLeft = 1
static let SlidingDrawerOpenRight = 2
var openSide:SlidingDrawerController.Drawers {
get{
return _openSide;
}
}
private var _openSide = SlidingDrawerController.Drawers.left
override func viewDidLoad() {
super.viewDidLoad()
// Instantiate VC's with storyboard ID's
self._leftViewController = self.instantiateViewControllers(storyboardID: self.leftViewControllerIdentifier)
self._centerViewController = self.instantiateViewControllers(storyboardID: self.centerViewControllerIdentifier)
self._rightViewController = self.instantiateViewControllers(storyboardID: self.rightViewControllerIdentifier)
self.drawDrawers(size: UIScreen.main.bounds.size)
self.view.addSubview(self.leftViewController.view)
self.view.addSubview(self.centerViewController.view)
self.view.addSubview(self.rightViewController.view)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(in: self.view, animation: { (UIViewControllerTransitionCoordinatorContext) -> Void in
// This is for beginning of transition
self.drawDrawers(size: size)
}, completion: { (UIViewControllerTransitionCoordinatorContext) -> Void in
// This is for after transition has completed.
})
}
// MARK: - Drawing View
func drawDrawers(size:CGSize) {
// Calculate Center View's Size
let centerWidth = (size.width/drawerSize) * (drawerSize - 1)
// Left Drawer
self.leftViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: size.width/self.drawerSize, height: size.height)
// Center Drawer
self.centerViewController.view.frame = CGRect(x: self.leftViewController.view.frame.width, y: 0.0, width: centerWidth, height: size.height)
// Right Drawer
self.rightViewController.view.frame = CGRect(x: self.centerViewController.view.frame.origin.x + self.centerViewController.view.frame.size.width, y: 0.0, width: size.width/self.drawerSize, height: size.height)
// Capture the Swipes
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeRightAction(rec:)))
swipeRight.direction = .right
centerViewController.view.addGestureRecognizer(swipeRight)
let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(self.swipeLeftAction(rec:)))
swipeLeft.direction = .left
centerViewController.view.addGestureRecognizer(swipeLeft)
openDrawer(openSide)
}
// MARK: - Open Drawers
func openDrawer(_ side:Drawers) {
self._openSide = side
var rect:CGRect
switch side{
case .left:
rect = CGRect(
x: 0.0,
y: 0.0,
width: self.view.bounds.width,
height: self.view.bounds.height
)
case .right:
rect = CGRect(
x: self.view.bounds.origin.x - self.leftViewController.view.bounds.size.width,
y: 0.0,
width: self.view.bounds.width,
height: self.view.bounds.height
)
}
UIView.animate(withDuration: 0.1, delay: 0, options: UIViewAnimationOptions.curveEaseIn, animations:
{ () -> Void in
// move views here
self.view.frame = rect
}, completion:
{ finished in
})
}
// MARK: - Swipe Handling
@objc func swipeRightAction(rec: UISwipeGestureRecognizer){
self.openDrawer(.left)
}
@objc func swipeLeftAction(rec:UISwipeGestureRecognizer){
self.openDrawer(.right)
}
// MARK: - Helpers
func instantiateViewControllers(storyboardID: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "\(storyboardID)")
}
}
Source here.
Image here.
Video here.
回答7:
I have implemented it in 2 ways.
First using Scroll View
and second using Container Views
.
You can have TableViewController
acting as a Menu in one container and a TabBarController
with hidden tabs in a second container. Pressing a Cell
in a Table View moves you to a n-th tab in Tab Bar.
All you have to do is animate the top container right on button click or a gesture. It then may look like this:
What is neat is that you can then easily add cool effects such as animating UIView using spring damping by using built-in methods to give it realistic bouncy effect. You can also add shadow to your main View (not added in the picture) to make it look like a page above the menu.