Reputation: 407
I'm using swipe gestures to navigate between tabs using a fake UITabBarController that hosts 4 UIViewController subclasses inside of a UIScrollView, with UIPanGestureRecognizers on each of them to detect swipes and switch tabs accordingly. The resulting effect is core navigation like what you see on Snapchat. However, one of these screens houses a map view (Mapbox map), which has its own gesture recognizers built-in.
I'd like for the user to be able to swipe on the map itself to navigate to another tab by detecting a swipe from the very right edge of the screen. I've tried adding a UIPanGestureRecognizer to an invisible UIView in this area on the right side, but this provides for a jerky, unanimated tab switch, and it intercepts touch events from the map below.
How can I allow for both gesture recognizers to receive events simultaneously, or filter out some events based upon only the start position of the swipe and its distance and direction?
Code for swiping view controller:
class KUSwipeViewController: EZSwipeController {
fileprivate var fakeTabBar: UIView!
fileprivate var tabBarHeight: CGFloat = 50
override func viewDidLoad() {
initUI()
}
override func setupView() {
super.setupView()
datasource = self
navigationBarShouldNotExist = true
}
func initUI() {
fakeTabBar = UIView(frame: CGRect(x: 0, y: Screen.height - tabBarHeight, width: Screen.width, height: tabBarHeight))
fakeTabBar.backgroundColor = .black
let tab0 = UIButton(frame: CGRect(x: Screen.width * 0.08, y: 9, width: 32, height: 32))
tab0.setBackgroundImage(#imageLiteral(resourceName: "tab-events"), for: .normal)
tab0.addTarget(self, action: #selector(tapped0), for: .touchUpInside)
fakeTabBar.addSubview(tab0)
let tab1 = UIButton(frame: CGRect(x: Screen.width * 0.32, y: 9, width: 32, height: 32))
tab1.setBackgroundImage(#imageLiteral(resourceName: "tab-feat"), for: .normal)
tab1.addTarget(self, action: #selector(tapped1), for: .touchUpInside)
fakeTabBar.addSubview(tab1)
let tab2 = UIButton(frame: CGRect(x: Screen.width * 0.58, y: 9, width: 32, height: 32))
tab2.setBackgroundImage(#imageLiteral(resourceName: "tab-chat"), for: .normal)
tab2.addTarget(self, action: #selector(tapped2), for: .touchUpInside)
fakeTabBar.addSubview(tab2)
let tab3 = UIButton(frame: CGRect(x: Screen.width * 0.82, y: 9, width: 32, height: 32))
tab3.setBackgroundImage(#imageLiteral(resourceName: "tab-profile"), for: .normal)
tab3.addTarget(self, action: #selector(tapped3), for: .touchUpInside)
fakeTabBar.addSubview(tab3)
view.addSubview(fakeTabBar)
}
func tapped0() {
self.moveToPage(0, animated: true)
}
func tapped1() {
self.moveToPage(1, animated: true)
}
func tapped2() {
self.moveToPage(2, animated: true)
}
func tapped3() {
self.moveToPage(3, animated: true)
}
}
extension KUSwipeViewController: EZSwipeControllerDataSource {
func viewControllerData() -> [UIViewController] {
let nav0 = UINavigationController()
let nav3 = UINavigationController()
let mapVC = EventMapViewController()
let featuredVC = FeaturedEventsViewController()
let chatVC = MessagesViewController()
let profileVC = ProfileViewController()
nav0.viewControllers = [mapVC]
nav3.viewControllers = [profileVC]
return [nav0, featuredVC, chatVC, nav3]
}
func titlesForPages() -> [String] {
return ["", "", "", ""]
}
func indexOfStartingPage() -> Int {
return 0
}
func changedToPageIndex(_ index: Int) {
Haptic.selection.generate()
}
}
Code for map view controller:
class EventMapViewController: CommonViewController {
fileprivate var mapView: MGLMapView!
fileprivate var statusBarView: UIView!
fileprivate var searchBar: FloatingSearchBar!
fileprivate var searchButton: UIButton!
fileprivate var filterButton: UIButton!
fileprivate var peekView: UIView!
fileprivate var architectView: UIView!
fileprivate var panArchitectView: UIView!
fileprivate var peekArchitectView: UIView!
fileprivate var peekLabel: UILabel!
fileprivate var panView: UIPanGestureRecognizer!
fileprivate let peekViewHeight: CGFloat = 140
fileprivate var selectedEvent: Event?
fileprivate var cardShowing: Bool = false
fileprivate var peekShowing: Bool = false
/*
override func statusBarStyle() -> UIStatusBarStyle {
return cardShowing ? .lightContent : .default
}
*/
override func navigationBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
initUI()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "eventSegue" {
let dvc = segue.destination as! EventViewController
dvc.event = selectedEvent!
}
}
func initUI() {
mapView = MGLMapView(frame: view.bounds)
mapView.styleURL = (UIColor.theme == .light) ? URL(string: mbThemeLight)! : URL(string: mbThemeDark)!
mapView.showsUserLocation = true
mapView.userTrackingMode = .none
mapView.delegate = self
mapView.setCenter(CLLocationCoordinate2D(latitude: 34, longitude: -118), zoomLevel: 5, animated: false)
view.addSubview(mapView)
mapView.snp.makeConstraints { (make) in
make.edges.equalTo(view)
}
//architect view (for intercepting touch)
architectView = UIView(frame: self.view.bounds)
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(EventMapViewController.handleTap(gestureRecognizer:)))
gestureRecognizer.delegate = self
architectView.addGestureRecognizer(gestureRecognizer)
architectView.isHidden = true
view.addSubview(architectView)
statusBarView = UIView()
statusBarView.backgroundColor = UIColor.clear
view.addSubview(statusBarView)
statusBarView.snp.makeConstraints { (make) in
make.left.right.top.equalTo(0)
make.height.equalTo(20)
}
searchButton = UIButton()
searchButton.backgroundColor = UIColor.kuLightBackground
searchButton.tintColor = (UIColor.theme == .light) ? UIColor.kuPrimary : UIColor.kuDeselected
searchButton.setImage(#imageLiteral(resourceName: "icon_search_white").withRenderingMode(.alwaysTemplate), for: .normal)
searchButton.layer.cornerRadius = 25
searchButton.layer.borderColor = UIColor.kuExtraLightBackground.cgColor
searchButton.layer.borderWidth = 1
searchButton.addTarget(self, action: #selector(searchButtonTapped(_:)), for: .touchUpInside)
view.addSubview(searchButton)
searchButton.snp.makeConstraints { (make) in
make.width.height.equalTo(50)
make.left.equalTo(24)
make.top.equalTo(34)
}
filterButton = UIButton()
filterButton.backgroundColor = UIColor.kuLightBackground
filterButton.tintColor = (UIColor.theme == .light) ? UIColor.kuPrimary : UIColor.kuDeselected
filterButton.setImage(#imageLiteral(resourceName: "icon_filter_white").withRenderingMode(.alwaysTemplate), for: .normal)
filterButton.layer.cornerRadius = 20
filterButton.layer.borderColor = UIColor.kuExtraLightBackground.cgColor
filterButton.layer.borderWidth = 1
filterButton.addTarget(self, action: #selector(filterButtonTapped(_:)), for: .touchUpInside)
view.addSubview(filterButton)
filterButton.snp.makeConstraints { (make) in
make.width.height.equalTo(40)
make.left.equalTo(80)
make.top.equalTo(40)
}
//peek view
peekView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height, width: UIScreen.main.bounds.width, height: peekViewHeight))
peekView.backgroundColor = UIColor(white: 1, alpha: 0.5)
peekView.layer.cornerRadius = 10
view.addSubview(peekView)
//peek label
peekLabel = UILabel(frame: CGRect(x: 0, y: 12, width: UIScreen.main.bounds.width, height: 68))
peekLabel.font = UIFont.kuBoldFont(ofSize: 38)
peekLabel.textColor = .black
peekLabel.textAlignment = .center
peekView.addSubview(peekLabel)
//peek architect
peekArchitectView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: peekViewHeight))
let peekGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(EventMapViewController.peekSelected(gestureRecognizer:)))
peekGestureRecognizer.delegate = self
peekArchitectView.addGestureRecognizer(peekGestureRecognizer)
peekView.addSubview(peekArchitectView)
loadEventAnnotations()
}
func enterPeekView() {
guard !peekShowing else { return }
architectView.isHidden = false
peekLabel.text = "\(selectedEvent!.title!) >"
peekView.cheetah
.move(0, peekViewHeight * -1)
.duration(0.18)
.easeOutExpo
.run()
peekShowing = true
}
func exitPeekView() {
guard peekShowing else { return }
architectView.isHidden = true
peekView.cheetah
.move(0, peekViewHeight)
.duration(0.18)
.easeInExpo
.run()
peekShowing = false
}
func loadEventAnnotations() {
RealmManager.shared.defaultRealm.objects(Event.self).forEach { (event) in
let annotation = EventAnnotation()
annotation.event = event
mapView.addAnnotation(annotation)
}
}
func searchButtonTapped(_ sender: UIButton) {
let eventSearchCardViewController = EventSearchCardViewController()
eventSearchCardViewController.delegate = self
UIApplication.rootViewController()?.presentCardViewController(eventSearchCardViewController)
}
func filterButtonTapped(_ sender: UIButton) {
let eventFilterCardViewController = EventFilterCardViewController()
eventFilterCardViewController.delegate = self
UIApplication.rootViewController()?.presentCardViewController(eventFilterCardViewController)
}
}
extension EventMapViewController: CardViewControllerDelegate {
func cardViewControllerWillAppear(cardViewController: CardViewController) {
cardShowing = true
setNeedsStatusBarAppearanceUpdate()
}
func cardViewControllerWillDisappear(cardViewController: CardViewController) {
cardShowing = false
setNeedsStatusBarAppearanceUpdate()
}
}
extension EventMapViewController: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
guard let eventAnnotation = annotation as? EventAnnotation else {
return
}
selectedEvent = eventAnnotation.event
enterPeekView()
}
func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
return false
}
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
var annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: UIColor.kuMarkerName)
if annotationImage == nil {
var image = UIImage(named: UIColor.kuMarkerName)!
image = image.withAlignmentRectInsets(UIEdgeInsets(top: 0, left: 0, bottom: image.size.height/2, right: 0))
annotationImage = MGLAnnotationImage(image: image, reuseIdentifier: UIColor.kuMarkerName)
}
return annotationImage
}
}
extension EventMapViewController: UIGestureRecognizerDelegate {
func handleTap(gestureRecognizer: UIGestureRecognizer) {
//close peek and keyboard on tap
view.endEditing(true)
if peekShowing {
exitPeekView()
}
}
func peekSelected(gestureRecognizer: UIGestureRecognizer) {
// performSegue(withIdentifier: "eventSegue", sender: nil)
let eventController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "EventViewController") as! EventViewController
self.navigationController?.pushViewController(eventController, animated: true)
}
}
Upvotes: 0
Views: 493
Reputation: 2680
To use multiple gestures make your class a GestureRecognizer delegate UIGestureRecoginizerDelegate
then use this function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer is UIPanGestureRecognizer || gestureRecognizer is UITapGestureRecognizer) {
return true
} else {
return false
}
}
Upvotes: 2