如何解决修改导航控制器以使其更像 swift 中的汉堡菜单
编辑
我通过添加自定义 UIViewControllerAnimatedTransitioning
来处理推送和弹出事件来实现这一点。它是 Robert Chen's 方法和 Fattie 的堆栈溢出答案的灵感。我还必须更新 SlidingNavigationController
(更新代码如下)。
主要问题是:
-
我希望当用户按下汉堡菜单图标时
FromVC
的导航栏可见。但这目前不会发生,因为在侧面导航的viewWillAppear
中我隐藏了导航栏。 -
interactivePopGestureRecognizer
不遵循自定义动画,因此当用户向右滑动边缘时,整个 VC 会从屏幕上滑动。
当汉堡图标被点击时:
当用户进行边缘滑动时:
新的自定义动画:
class RevealSideNav: NSObject,UIViewControllerAnimatedTransitioning {
var pushStyle: Bool = false
var prevIoUslyHiddenVC: UIViewController.Type = MainVC.self
var oldSnapshot: UIView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: .from),let toVC = transitionContext.viewController(forKey: .to)
else { return }
if pushStyle {
hideSidenav(using: transitionContext)
return
}
let initalScale = MenuHelper.initialMenuScale
let containerView = transitionContext.containerView
containerView.backgroundColor = MenuHelper.menuBGColor
toVC.view.transform = CGAffineTransform(scaleX: initalScale,y: initalScale)
containerView.insertSubview(toVC.view,belowSubview: fromVC.view)
// fromVC.navigationController?.navigationBar.isHidden = false
fromVC.view.isHidden = true
guard let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false) else { return }
snapshot.isUserInteractionEnabled = false
snapshot.tag = MenuHelper.snapshotNumber
snapshot.layer.shadowOpacity = MenuHelper.snapshotOpacity
containerView.insertSubview(snapshot,aboveSubview: toVC.view)
fromVC.view.isHidden = true
UIView.animate(withDuration: transitionDuration(using: transitionContext),animations: {
snapshot.center.x += UIScreen.main.bounds.width * MenuHelper.menuWidth
snapshot.layer.opacity = MenuHelper.snapshotOpacity
toVC.view.transform = CGAffineTransform(scaleX: 1,y: 1)
},completion: { _ in
fromVC.view.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
self.oldSnapshot = snapshot
}
)
}
func hideSidenav(using transitionContext: UIViewControllerContextTransitioning) {
let fz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let tz = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let f = transitionContext.finalFrame(for: tz)
let fOff = f.offsetBy(dx: UIScreen.main.bounds.width * MenuHelper.menuWidth,dy: 0)
tz.view.frame = fOff
transitionContext.containerView.insertSubview(tz.view,aboveSubview: fz.view)
UIView.animate(
withDuration: transitionDuration(using: transitionContext),animations: {
self.oldSnapshot.removeFromSuperview()
tz.view.frame = f
},completion: {_ in
transitionContext.completeTransition(true)
})
}
}
我想在我的应用中添加对汉堡菜单的支持。在我使用基于转换委托的非常复杂的逻辑并测量用户为完成子视图的滑出动画而滑动的次数之前(基于 Robert Chen 的 iOS Tutorial: How to make a customizable interactive slide-out menu in Swift)。 Here's 旧的 stackoverflow 帖子,其中包含我之前的汉堡菜单代码。
这是旧菜单的样子:
我意识到通过在内置 UINavigationController 中添加我的 SideNav View 控制器可以更好地实现滑出功能。而且我认为这种方法不太容易发生内存泄漏。所以这就是我所做的。创建了我的旧 SideNav 的新副本。然后我将它嵌入到自定义 UINavigationController
中,该自定义 self.navigationController?.pushViewController
具有预先配置的方法来处理返回的幻灯片。最后将我确实选择的行操作更新为 @objc func closeViewWithPan(sender: UIPanGestureRecognizer) {
guard let calledFromVC = calledFromVC else { return }
print("Presenting VC: ",navigationController?.presentingViewController)
if navigationController?.presentingViewController == nil {
navigationController?.pushViewController(calledFromVC,animated: true)
}
Analytics.logEvent(AnalyticsEvent.HideSideNav.rawValue,parameters: [StringAnalyticsProperties.VCdisplayed.rawValue : "\(type(of: calledFromVC))".lowercased()])
}
。呈现和返回侧面导航时,结果看起来非常好。
但是这种方法缺少一件重要的事情。传统的汉堡菜单在屏幕的一侧仍然可见旧视图控制器的一部分,以便用户可以在其上滑动以将其带回来。我部分尝试通过向侧面导航添加平移手势并呈现旧视图控制器来实现此功能。
*** Terminating app due to uncaught exception 'NSinvalidargumentexception',reason: '<Speech_Drill.SlidingNavigationController: 0x10a810c00> is pushing the same view controller instance (<Speech_Drill.MainVC: 0x105848600>) more than once which is not supported and is most likely an error in the application : xxx'
terminating with uncaught exception of type NSException
但我立即遇到了这个问题:
func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
let sideNav = SideNavigationController()
let sideNavigationController = SlidingNavigationController.init(rootViewController: sideNav)
self.window?.rootViewController = sideNavigationController
...
}
所以我想知道是否有一种方法可以让之前展示的 VC 的一部分仍然像传统的汉堡菜单一样粘在右边,可以滑动到前面。此外,如果可能的话,我希望在应用启动时默认显示第一个视图控制器,而不是侧面导航。
应用委托设置:
class SlidingNavigationController: UINavigationController,UIGestureRecognizerDelegate,UINavigationControllerDelegate{
let revealSideNav = RevealSideNav()
override func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
override func pushViewController(_ viewController: UIViewController,animated: Bool) {
super.pushViewController(viewController,animated: animated)
interactivePopGestureRecognizer?.isEnabled = false
}
func navigationController(_ navigationController: UINavigationController,didShow viewController: UIViewController,animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = true
}
// IMPORTANT: without this if you attempt swipe on
// first view controller you may be unable to push the next one
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
func navigationController(
_ navigationController: UINavigationController,animationControllerFor operation: UINavigationControllerOperation,from fromVC: UIViewController,to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
revealSideNav.pushStyle = operation == .push
return revealSideNav
}
}
自定义 UINavigationController:
class SideNavigationController: UIViewController {
private let noticesUrl = "https://github.com/parthv21/Speech-Drill/blob/master/Speech-Drill/information/info.json"
private let sideNavMenuItemReuseIdentifier = "SideNavMenuItemIdentifier"
static let sideNav = SideNavVC()
var interactor: Interactor? = nil
var calledFromVC: UIViewController?
private let sideNavContainer: UIView
private let sideNavTableView: UITableView
private let sideNavNoticesTableViewCell: SideNavNoticesTableViewCell
private let sideNavAdsTableViewCell: SideNavAdsTableViewCell
private let versionInfoView: VersionInfoView
private var menuItems = [sideNavMenuItemStruct]()
var selectedindex = 1
override init(nibName nibNameOrNil: String?,bundle nibBundleOrNil: Bundle?) {
sideNavContainer = UIView()
sideNavTableView = UITableView()
sideNavNoticesTableViewCell = SideNavNoticesTableViewCell()
sideNavAdsTableViewCell = SideNavAdsTableViewCell()
versionInfoView = VersionInfoView()
super.init(nibName: nibNameOrNil,bundle: nibBundleOrNil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
sideNavTableView.delegate = self
sideNavTableView.dataSource = self
sideNavTableView.register(SideNavMenuItemCell.self,forCellReuseIdentifier: sideNavMenuItemReuseIdentifier)
sideNavTableView.separatorStyle = .none
sideNavNoticesTableViewCell.fetchNotices()
sideNavAdsTableViewCell.fetchAds()
configureSideNav()
let panGesture = UIPanGestureRecognizer(target: self,action: #selector(closeViewWithPan(sender:)))
view.addGestureRecognizer(panGesture)
view.backgroundColor = MenuHelper.menuBGColor
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
navigationController?.setNavigationBarHidden(true,animated: animated)
guard let calledFromVC = calledFromVC else { return }
for (index,item) in menuItems.enumerated() {
if item.presentedVC.isKind(of: type(of: calledFromVC)) {
let indexPath = IndexPath(item: index + 1,section: 0)
sideNavTableView.selectRow(at: indexPath,animated: false,scrollPosition: .none)
selectedindex = index
break
}
}
}
override func viewWilldisappear(_ animated: Bool) {
super.viewWilldisappear(animated)
navigationController?.setNavigationBarHidden(false,animated: animated)
}
func configureSideNav() {
view.addSubview(sideNavContainer)
sideNavContainer.translatesAutoresizingMaskIntoConstraints = false
sideNavContainer.addSubview(versionInfoView)
versionInfoView.translatesAutoresizingMaskIntoConstraints = false
//Storyboard Based VC
let storyboard = UIStoryboard(name: "Main",bundle: nil)
let mainVC = storyboard.instantiateViewController(withIdentifier: "MainVC") as! MainVC
let infoVC = storyboard.instantiateViewController(withIdentifier: "InfoVC") as! InfoVC
//Fully programatic VC - There is a reference to this in the VC too which I think will cause memory leaks
let discussionsVC = discussionsViewController()
// calledFromVC = mainVC
let mainVcmenuItem = sideNavMenuItemStruct(itemName: "Recordings",itemImg: recordIcon,itemImgClr: accentColor,presentedVC: mainVC)
let infoVcmenuItem = sideNavMenuItemStruct(itemName: "About",itemImg: infoIcon,presentedVC: infoVC)
let discussionsVcmenuItem = sideNavMenuItemStruct(itemName: "discussions",itemImg: discussionIcon,presentedVC: discussionsVC) //Look into using SF Symbols with UIImage(systemName: T##String)
menuItems.append(mainVcmenuItem)
menuItems.append(discussionsVcmenuItem)
menuItems.append(infoVcmenuItem)
sideNavTableView.allowsMultipleSelection = false
sideNavContainer.addSubview(sideNavTableView)
sideNavTableView.translatesAutoresizingMaskIntoConstraints = false
sideNavTableView.backgroundColor = .clear
NSLayoutConstraint.activate([
sideNavContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),sideNavContainer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,constant: 30),sideNavContainer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,constant: -70),sideNavContainer.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),versionInfoView.bottomAnchor.constraint(equalTo: sideNavContainer.bottomAnchor),versionInfoView.leadingAnchor.constraint(equalTo: sideNavContainer.leadingAnchor,constant: -8),versionInfoView.trailingAnchor.constraint(equalTo: sideNavContainer.trailingAnchor,constant: 8),sideNavTableView.topAnchor.constraint(equalTo: sideNavContainer.topAnchor),sideNavTableView.leadingAnchor.constraint(equalTo: sideNavContainer.leadingAnchor),sideNavTableView.trailingAnchor.constraint(equalTo: sideNavContainer.trailingAnchor),sideNavTableView.bottomAnchor.constraint(equalTo: versionInfoView.topAnchor)
])
}
@objc func closeViewWithPan(sender: UIPanGestureRecognizer) {
guard let calledFromVC = calledFromVC else { return }
print("Presenting VC: ",parameters: [StringAnalyticsProperties.VCdisplayed.rawValue : "\(type(of: calledFromVC))".lowercased()])
}
}
extension SideNavigationController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
return menuItems.count + 2
}
func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
return sideNavNoticesTableViewCell
}
if indexPath.row == menuItems.count + 1 {
return sideNavAdsTableViewCell
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: sideNavMenuItemReuseIdentifier) as? SideNavMenuItemCell else { return UITableViewCell() }
cell.configureCell(with: menuItems[indexPath.row - 1])
return cell
}
func tableView(_ tableView: UITableView,heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 190
} else if indexPath.row == menuItems.count + 1 {
return 300
}
return 40
}
func tableView(_ tableView: UITableView,didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 || indexPath.row == menuItems.count + 1 { return }
let vcToPresent = menuItems[indexPath.row - 1].presentedVC
calledFromVC = vcToPresent
vcToPresent.modalPresentationStyle = .fullScreen
self.navigationController?.pushViewController(vcToPresent,animated: true)
}
}
侧面导航视图控制器:
var timer = false
var autoClicker = 1
setInterval(function() {
if (Game.hasBuff("Clot") && timer === false) {
autoClicker = setInterval(Game.ClickCookie,1);
timer = true;
} else if (Game.hasBuff("Clot") && timer === true) {
return "test"
}else {
clearInterval(autoClicker);
timer = false}
},1000);
当前结果:
这就是我的想法:(希望可以配置可见的侧导航vc的宽度。)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。