如何将幻灯片 out/burger 菜单合并到现有的选项卡栏控制器

How do I incorporate a slide out/burger menu to an existing tab bar controller

所以我想创建一个滑出式菜单。我希望菜单在触摸标签栏 item/button 时滑出。 到目前为止,我已经创建了一个带有 4 个不同标签栏按钮的标签视图控制器。每个按钮都指向一个不同的视图控制器,每个视图控制器都被分离到它们自己的故事板中。

我尝试将侧边菜单的 UIViewController 添加到已经建立为 UITabBarController 的 class,但出现错误:

从 classes 'UITabBarController' 和 'UIViewController' 多重继承。

有办法解决这个问题吗?

感谢大家的帮助

不要使用 UITabBarController,而是使用一个视图控制器和按钮操作来管理选项卡控件。

应用往往有一个像 Tab Bar Controller 这样的顶级容器,不能嵌入到视图中。这里的做法是将主体和左侧菜单分别包裹在一个Container View中。现在这两个元素都可以放在包装器中 View Controller.

一个Scroll View用来模拟左移菜单打开和关闭菜单on/off-screen.

包含视图控制器

View Controller 放到故事板上。这是您进入应用程序的入口点。 选中 Is Initial View Controller 的框。 将 Simulated SizeFixed 更改为 Freeform。将宽度设置为 568,这样我们就可以并排放置菜单和标签栏。 创建一个新的 Swift 文件并将此 View Controller 的 class 设置为 ContainerVC。

import UIKit
class ContainerVC : UIViewController {
}

滚动视图

在 Container View Controller 中,放入 Scroll View 并在各个方向添加约束。

选中 Scrolling Enabled 的复选框。这允许您平移屏幕以滑动菜单。如果您的应用使用水平滚动元素,您可能需要禁用此功能。

选中 Paging Enabled 的框。这会将菜单捕捉到打开或关闭状态。

取消选中 Bounces 的复选框。您真的不想滚动到选项卡栏控制器的右边缘。

将 IBOutlet 连接到 ContainerVC:

@IBOutlet weak var scrollView: UIScrollView!

左容器

左边的容器包含菜单,不是整个屏幕的宽度。

Container View拖到Scroll View的左侧。

为包含 Scroll View.

的顶部、左侧和右侧添加约束

将宽度硬编码为 260。

使用 ContainerVC 的嵌入式视图为 Equal height 添加约束。注意:不要将高度限制为滚动视图。 删除 Container View.

附带的嵌入 View Controller

将新的 Table View Controller(根据您需要的任何视图)放到故事板上,并使用嵌入转场连接。

右容器

右边的容器包含应用程序的主体,即 Tab Bar Controller

将第二个 Container View 拖到 Scroll View 的右侧。

为包含 Scroll View 的顶部、右侧和底部添加约束。水平连接到您之前创建的左侧容器视图。

Equal heightEqual width 都约束到 ContainerVC 的嵌入式视图。

注意:不要将这些限制到滚动视图。

同样,删除随 Container View 免费提供的嵌入式视图控制器。相反,创建一个 embed segue 到 Tab Bar Controller.

要在两个容器之间创建一点视觉分离,请将 Runtime Attribute 添加到 Right Container。添加 layer.shadowOpacity0.8.

制表符

将每个选项卡嵌入 Navigation Controller。这样做可以免费获得 Navigation Bar.

拖一个Bar Button Item到每个Navigation Bar的左上角。

IBAction 连接到每个控制器。这些将向曾祖父 ContainerVC 发出 Notification 以切换菜单。

@IBAction func toggleMenu(sender: AnyObject) {
  NotificationCenter.default().post(name: Notification.Name("toggleMenu"), object: nil)
}

最后将以下代码添加到 ContainerVC 中:

class ContainerVC : UIViewController {

    // This value matches the left menu's width in the Storyboard
    let leftMenuWidth:CGFloat = 260

    // Need a handle to the scrollView to open and close the menu
    @IBOutlet weak var scrollView: UIScrollView!

    override func viewDidLoad() {

        // Initially close menu programmatically.  This needs to be done on the main thread initially in order to work.
        DispatchQueue.main.async() {
            self.closeMenu(animated: false)
        }

        // Tab bar controller's child pages have a top-left button toggles the menu
        NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.toggleMenu), name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)

        NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.closeMenuViaNotification), name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)

        // Close the menu when the device rotates
        NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

        // LeftMenu sends openModalWindow
        NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.openModalWindow), name: NSNotification.Name(rawValue: "openModalWindow"), object: nil)

    }

    // Cleanup notifications added in viewDidLoad
    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc func openModalWindow() {
        performSegue(withIdentifier: "openModalWindow", sender: nil)
    }

    @objc func toggleMenu() {
        scrollView.contentOffset.x == 0  ? closeMenu() : openMenu()
    }

    // This wrapper function is necessary because closeMenu params do not match up with Notification
    @objc func closeMenuViaNotification(){
        closeMenu()
    }

    // Use scrollview content offset-x to slide the menu.
    func closeMenu(animated:Bool = true){
        scrollView.setContentOffset(CGPoint(x: leftMenuWidth, y: 0), animated: animated)
    }

    // Open is the natural state of the menu because of how the storyboard is setup.
    func openMenu(){
        print("opening menu")
        scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
    }

    @objc func rotated(){
        if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
            DispatchQueue.main.async() {
                print("closing menu on rotate")
                self.closeMenu()
            }
        }
    }
}

extension ContainerVC : UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("scrollView.contentOffset.x:: \(scrollView.contentOffset.x)")
    }

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        scrollView.isPagingEnabled = true
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        scrollView.isPagingEnabled = false
    }
}

代码完成以下内容:

  • 收听 "toggleMenu" 通知
  • 根据打开或关闭实现toggleMenu方法 当前 contentOffset.x
  • 通过更改 contentOffset-x 打开和关闭菜单。

希望您有一个简单的滑出式左侧菜单,可以在其上构建应用程序的其余部分。

@Ajo 回答正确。在容器视图中嵌入汉堡包和标签栏。

完整的源代码你可以从下面的link中找到

source code link

还有我找到的详细教程 tutorial link

使用初始视图控制器作为容器视图。 使用滑入/滑出通知