Swift中的多视图控制器导航

时间:2018-10-30 06:23:07

标签: swift animation menu navigation segue

Swift中的侧边菜单导航

Swift 4.2,Xcode 10.0

我的最终目标是能够通过我的视图控制器轻松(正常)横向/导航,如下所示。

Navigation

我希望能够使用通用的侧菜单从视图控制器导航到视图控制器。至此,我想出了一种令人难以置信的 hacky 方法来实现此目的,其中当我在侧面菜单中选择视图控制器时,它会关闭侧面菜单,然后先呈现下一个视图从那个控制器,并假设它不是目标视图控制器,当我从那个视图控制器搜索到目标视图控制器时,一个窗口临时覆盖了它的内容。

Path through storyboard

要从侧面菜单显示的视图控制器的唯一路径。

Animation Paths 注意: 上图中的每种颜色是应用程序在侧面菜单中轻按某些内容后可以采取的另一条路径。

此实现有很多错误,我什至都不知道从哪里开始。在极少数情况下,您可以在命令之间看到中间视图控制器的内容。同样,由于在某些情况下实际到达目标视图所需的数量过多,动画可能会有点断断续续和失真。更不用说现在要在我的侧边菜单中添加另一行所需要的巨大难度和复杂性。而且我非常清楚这是残酷的,正在拼命地寻找解决方案,而不是我的复杂导航问题。最近,我尝试过使用容器视图,并将侧面菜单放在堆栈的底部,而不是现在的顶部,但是并没有什么用。


在过去的几周里,我一直在发疯,试图找出如何做到这一点。我发现了无数种侧边菜单的实现,但是到目前为止,我发现的所有内容只会在一个视图控制器上仅显示侧边菜单,而不是在所有侧边菜单的目标上都显示出来,如果它们都是在同一水平上可以这么说。从本质上讲,侧面菜单必须能够出现在所有3个视图控制器上,并且无需在视图控制器上进行 hacking 搜索。同样,如果此侧面菜单易于扩展,那将是非常理想的,这样我就可以轻松地向侧面菜单添加多个部分。


2 个答案:

答案 0 :(得分:4)

enter image description here

我已经为这个问题创建了一个示例项目。您可以在上图中看到输出。基本上我所做的就是围绕补充工具栏创建了一个包装器类,然后在需要的时候使用它:)

侧边栏

import UIKit
protocol SidebarDelegate {
    func sidbarDidOpen()
    func sidebarDidClose(with item: Int?)
}
class SidebarLauncher: NSObject{

    var view: UIView?
    var delegate: SidebarDelegate?
    var vc: NavigationViewController?
    init(delegate: SidebarDelegate) {
        super.init()
        self.delegate = delegate
    }

    func show(){
        let bounds = UIScreen.main.bounds
        let v = UIView(frame: CGRect(x: -bounds.width, y: 0, width: bounds.width, height: bounds.height))
        v.backgroundColor = .clear
        let vc = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavigationController") as! NavigationViewController
        v.addSubview(vc.view)
        vc.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            vc.view.topAnchor.constraint(equalTo: v.topAnchor),
            vc.view.leadingAnchor.constraint(equalTo: v.leadingAnchor),
            vc.view.bottomAnchor.constraint(equalTo: v.bottomAnchor),
            vc.view.trailingAnchor.constraint(equalTo: v.trailingAnchor, constant: -60)
            ])
        vc.delegate = self
        v.isUserInteractionEnabled = true
        v.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:))))
        self.view = v
        self.vc = vc
        UIApplication.shared.keyWindow?.addSubview(v)

        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
            self.view?.frame = CGRect(x: 0, y: 0, width: self.view!.frame.width, height: self.view!.frame.height)
            self.view?.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        }, completion: {completed in
            self.delegate?.sidbarDidOpen()
        })

    }

    @objc func handleTapGesture(_ sender: UITapGestureRecognizer){
        closeSidebar(option: nil)
    }
    func closeSidebar(option: Int?){
        UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [.curveEaseOut], animations: {
            if let view = self.view{
                view.frame = CGRect(x: -view.frame.width, y: 0, width: view.frame.width, height: view.frame.height)
                self.view?.backgroundColor = .clear

            }
        }, completion: {completed in
            self.view?.removeFromSuperview()
            self.view = nil
            self.vc = nil
            self.delegate?.sidebarDidClose(with: option)
        })
    }

}
extension SidebarLauncher: NavigationDelegate{
    func navigation(didSelect: Int?) {
        closeSidebar(option: didSelect)
    }
}

NavigationController

import UIKit
protocol NavigationDelegate{
    func navigation(didSelect: Int?)
}

class NavigationViewController: UIViewController{

    @IBOutlet weak var buttonLaunchVC: UIButton!
    @IBOutlet weak var buttonSecondViewController: UIButton!
    @IBOutlet weak var buttonThirdViewController: UIButton!


    var delegate: NavigationDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        [buttonLaunchVC,buttonSecondViewController,buttonThirdViewController].forEach({
            $0?.addTarget(self, action: #selector(didSelect(_:)), for: .touchUpInside)
        })
    }

    @objc func didSelect(_ sender: UIButton){
        switch sender {
        case buttonLaunchVC:
            delegate?.navigation(didSelect: 0)
        case buttonSecondViewController:
            delegate?.navigation(didSelect: 1)
        case buttonThirdViewController:
            delegate?.navigation(didSelect: 2)
        default:
            break
        }
    }


    @IBAction func CloseMenu(_ sender: Any) {
        delegate?.navigation(didSelect: nil)
    }


}

ViewController

class ViewController: UIViewController {

    @IBAction func OpenMenu(_ sender: Any) {
        SidebarLauncher(delegate: self ).show()
    }

}
extension ViewController: SidebarDelegate{
    func sidbarDidOpen() {
        print("Sidebar opened")
    }

    func sidebarDidClose(with item: Int?) {
        guard let item = item else {return}
        print("Did select \(item)")
        switch item {
        case 0:
           break
        case 1:
            let v = UIStoryboard.main.SecondVC()
            present(v!, animated: true)
        case 2:
            let v = UIStoryboard.main.ThirdVC()
            present(v!, animated: true)
        default:
            break
        }
    }

主要兴趣领域是 SidebarLauncher 类 它的作用:调用 show()方法时。它创建一个UIView,然后将其添加到键窗口(即您当前的View)中,然后添加NavigationController。

要设置与边栏的通信,我创建了两个协议

  1. SidebarDelegate:

侧边栏委托是主要协议,通过它可以了解用户是否选择了任何ViewController。

  1. NavigationDelegate: 该协议用于包装程序和导航控制器之间的通信。当用户点击任意按钮时。它通知包装类有关此信息。

包装器类具有方法 closeSidebar ,该方法然后关闭侧边栏并通知Controller类,该侧边栏已通过选项关闭。

在sidebarDidClose中,您可以决定如何处理用户所做的选择。

我有点着急,这就是为什么我使用 Int 的原因,而您应该考虑使用适合于需要确定要打开哪个ViewController的struct或class。

https://github.com/sahilmanchanda2/SidebarTest

答案 1 :(得分:1)