如何将幻灯片 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 Size
从 Fixed
更改为 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 height
和 Equal width
都约束到 ContainerVC 的嵌入式视图。
注意:不要将这些限制到滚动视图。
同样,删除随 Container View
免费提供的嵌入式视图控制器。相反,创建一个 embed
segue 到 Tab Bar Controller
.
要在两个容器之间创建一点视觉分离,请将 Runtime Attribute
添加到 Right Container。添加 layer.shadowOpacity
与 0.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中找到
还有我找到的详细教程
tutorial link
使用初始视图控制器作为容器视图。
使用滑入/滑出通知
所以我想创建一个滑出式菜单。我希望菜单在触摸标签栏 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 Size
从 Fixed
更改为 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 height
和 Equal width
都约束到 ContainerVC 的嵌入式视图。
注意:不要将这些限制到滚动视图。
同样,删除随 Container View
免费提供的嵌入式视图控制器。相反,创建一个 embed
segue 到 Tab Bar Controller
.
要在两个容器之间创建一点视觉分离,请将 Runtime Attribute
添加到 Right Container。添加 layer.shadowOpacity
与 0.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中找到
还有我找到的详细教程 tutorial link
使用初始视图控制器作为容器视图。 使用滑入/滑出通知