Dragging with UIPanGestureRecognizer
Twitter Slide Out Menu
Now that our menu component is fully refactored into its own extension area, we're ready to investigate how we can apply a dragging behavior to perform the reveal and hide functionality. This is going to first require an implementation of UIPanGestureRecognizer and determining where our user is touching. One bug you'll notice after the implementation is that our Menu has lot its interaction with touches. We'll fix this in the next video by allowing gestures to interact with other components in our app.

Comments (6)
Te-Jen Wu
4 years ago
Hi Brian, I found if adding more rows on the HomeController, the tableView cannot work any more. Does the panGesture we added conflict with the tableView in the HomeController?
Brian Voong
4 years ago
Tube
4 years ago
I don't want to slide the home vc over. I want to just overlay the menu vc.
rehannali
4 years ago
I think you need to set some properties on menu view controller. I believe you can use vc.modalTransitionStyle = .crossDissolve vc.modalPresentationStyle = .overCurrentContext and then present over vc and to remove you have dismiss it. I think that is the answer to your question.
Tube
4 years ago
Thx. I'll try that!
Tube
4 years ago
Oh, I see now. It is this transform that moves it over: self.view.transform = transform.
Jeffrey Chang
4 years ago
i have you have 1.5 x speed and you sound like you had too much coffee lol
Sylvain
4 years ago
Hello Brian, There's something I don't understand. We used those function to stop our views to be dragged too much to the left or right : x = min(menuWidth, x) x = max(0,x) If I set the menuController and navigationController transform like that, it doesn't work. menuController.view.transform = CGAffineTransform(translationX: translation.x, y: 0) navigationController?.view.transform = CGAffineTransform(translationX: translation.x, y: 0) But if write it like you did, it works. let transform = CGAffineTransform(translationX: x, y: 0) menuController.view.transform = transform navigationController?.view.transform = transform I really don't understand the difference. It should be equivalent, shouldn't be ?
Brian Voong
4 years ago
Sylvain
4 years ago
Oh of course I haven't seen it... Thank you Brian !
smiller193
4 years ago
Hello Brian, I am presenting my menu from the right side using this code block here @objc func setupBarButtonItems(){ let mainWindow = UIApplication.shared.keyWindow //initial position of side menu menuController.view.frame = CGRect(x: maxX, y: 0, width: menuWidth, height: self.view.frame.height) mainWindow?.addSubview(menuController.view) addChildViewController(menuController) //add side menu button to nav bar let sideMenuButton = UIBarButtonItem(image: UIImage(named: "icons8-Menu-48")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(self.rightBarPressed)) let calendarButton = UIBarButtonItem(image: UIImage(named: "icons8-calendar-50")?.withRenderingMode(.alwaysOriginal), style: .plain, target: self, action: #selector(self.presentCalendar)) navigationItem.rightBarButtonItems = [sideMenuButton,calendarButton] } @objc func addPanGesture(){ let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) self.view.addGestureRecognizer(panGesture) } @objc func handlePan(gesture: UIPanGestureRecognizer){ //will give you position of finger in swipe gesture let translation = gesture.translation(in: view) if gesture.state == .changed { var x = translation.x print(x) x = min(-menuWidth,x) x = max(0,-returnX) let transform = CGAffineTransform(translationX: x , y: 0) menuController.view.transform = transform navigationController?.view.transform = transform }else if gesture.state == .ended { rightBarPressed() } //drag out the menu controller } However my dragging is a little flawed. It accurately comes out but when I try to move it back it snaps back to the pulled out state. Im aware that swipes to the left are in the negative direction and vice versa for right. Any idea how I would change the min and max functions to make this work properly
smiller193
4 years ago
SO post for refrence https://stackoverflow.com/questions/52990592/pangesture-issue
Brian Voong
4 years ago
smiller193
4 years ago
yeah I understand that but is it not supposed to at least be able to slide in and out without flaw following this video?
smiller193
4 years ago
I am watching the next video and I still can't get it to work when coming from the right...any ideas?
darren100
4 years ago
Keywindow is returning nil and wouldn't setUpViews in viewDidLoad. I'm trying to implement this into the firebase app. also tried using if let and it tells it keyWindow is nil: import UIKit import Firebase class MainTabBarController: UITabBarController, UITabBarControllerDelegate { override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } override func viewDidLoad() { super.viewDidLoad() self.delegate = self setUpViews() setUpMenuView() if Auth.auth().currentUser == nil { //runs when all the UI is loaded, if you dont dispatchque this the UI will not load and this will crash. DispatchQueue.main.async { let navigationController = UINavigationController(rootViewController: SignInViewController()) self.present(navigationController, animated: true, completion: nil) } } else { } navigationItem.leftBarButtonItem = UIBarButtonItem(title: "slide", style: .done, target: self, action: #selector(handleOpen)) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan)) view.addGestureRecognizer(panGesture) } // MARK:- Menu view let menu = MenuController() fileprivate let menuWidth: CGFloat = 300 func setUpMenuView() { menu.view.frame = CGRect(x: 0, y: 0, width: menuWidth, height: view.frame.height) let keyWindow = UIApplication.shared.keyWindow keyWindow?.addSubview(menu.view) addChild(menu) } fileprivate func performAnimation(transform: CGAffineTransform) { UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: { [unowned self] in self.menu.view.transform = transform self.navigationController?.view.transform = transform }) } @objc func handlePan(gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) print(translation) if gesture.state == .changed { var x = translation.x // x = max(x, 300) let transform = CGAffineTransform(translationX: x, y: 0) menu.view.transform = transform } } @objc func handleOpen() { // performAnimation(transform: CGAffineTransform(translationX: menuWidth, y: 0)) } @objc func handleHide() { performAnimation(transform: .identity) } // MARK:- ViewSetUp func setUpViews() { let image = createImage(name: "home", width: 25, height: 25) let messageImag = createImage(name: "messages", width: 25, height: 25) let profileImg = createImage(name: "profile", width: 25, height: 25) let notificationImg = createImage(name: "notifications", width: 25, height: 25) let home = createTab(unselected_image: image , selected_image: image , rootViewController: PhotoSelectorController(collectionViewLayout: UICollectionViewFlowLayout())) let messages = createTab(unselected_image: messageImag , selected_image: messageImag , rootViewController: PhotoSelectorController(collectionViewLayout: UICollectionViewFlowLayout())) let profile = createTab(unselected_image: profileImg , selected_image: profileImg , rootViewController: PhotoSelectorController(collectionViewLayout: UICollectionViewFlowLayout())) let notification = createTab(unselected_image: notificationImg , selected_image: notificationImg , rootViewController: PhotoSelectorController(collectionViewLayout: UICollectionViewFlowLayout())) self.viewControllers = [home,profile,notification,messages] //lines up the buttons // guard let item = tabBar.items else {return} // for controller in item { // controller.imageInsets = UIEdgeInsets(top: 4, left: 0, bottom: 0, right: -4) // } } fileprivate func createImage(name: String, width: CGFloat, height: CGFloat) -> UIImage { if let image = UIImage(named: name)?.resizeImage(targetSize: CGSize(width: width, height: height)) { return image } else { print("Error image creation") return UIImage() } } fileprivate func createTab(unselected_image: UIImage, selected_image: UIImage, rootViewController: UIViewController = UIViewController()) -> UINavigationController { let navController = UINavigationController(rootViewController: rootViewController) navController.tabBarItem.image = selected_image navController.tabBarItem.selectedImage = selected_image return navController } }
darren100
4 years ago
BOOTLEG FIX: Call the method in viewDidLayOutSubviews().
peterjf2
2 years ago
This worked for me when I moved my initial position code blocks into viewDidLoad and the menu stopped being displayed. Thanks!
HELP & SUPPORT