I'm building an application in swift which gets user's location and save it to access later. I have a problem about UI. I made a left-side menu on mapView. When I want to slide to open it, map is sliding but side-menu doesn't opening. Besides, I have a BarButtonItem on top bar and if I slide from there side-menu is opening. I mean, I think the problem is about sliding operation with map and any other thing are not working properly.
I slide it from bar button item in this picture.
override func viewDidLoad() {
super.viewDidLoad()
centerViewController = UIStoryboard.userMapViewController()
centerViewController.delegate = self
// wrap the centerViewController in a navigation controller, so we can push views to it
// and display bar button items in the navigation bar
centerNavigationController = UINavigationController(rootViewController: centerViewController)
view.addSubview(centerNavigationController.view)
addChild(centerNavigationController)
centerNavigationController.didMove(toParent: self)
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
centerNavigationController.view.addGestureRecognizer(panGestureRecognizer)
}
func toggleLeftPanel() {
let notAlreadyExpanded = (currentState != .leftPanelExpanded)
if notAlreadyExpanded {
addLeftPanelViewController()
}
animateLeftPanel(shouldExpand: notAlreadyExpanded)
}
func addLeftPanelViewController() {
guard leftViewController == nil else { return }
if let vc = UIStoryboard.leftViewController() {
addChildSidePanelController(vc)
leftViewController = vc
}
}
func animateLeftPanel(shouldExpand: Bool) {
if shouldExpand {
currentState = .leftPanelExpanded
animateCenterPanelXPosition(
targetPosition: centerNavigationController.view.frame.width - centerPanelExpandedOffset)
} else {
animateCenterPanelXPosition(targetPosition: 0) { _ in
self.currentState = .leftPanelCollapsed
self.leftViewController?.view.removeFromSuperview()
self.leftViewController = nil
}
}
}
extension ContainerViewController: UIGestureRecognizerDelegate {
#objc func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
let gestureIsDraggingFromLeftToRight = (recognizer.velocity(in: view).x > 0)
switch recognizer.state {
case .began:
if currentState == .leftPanelCollapsed {
if gestureIsDraggingFromLeftToRight {
addLeftPanelViewController()
}
showShadowForCenterViewController(true)
}
case .changed:
if let rview = recognizer.view {
rview.center.x = rview.center.x + recognizer.translation(in: view).x
recognizer.setTranslation(CGPoint.zero, in: view)
}
case .ended:
if let _ = leftViewController,
let rview = recognizer.view {
// animate the side panel open or closed based on whether the view
// has moved more or less than halfway
let hasMovedGreaterThanHalfway = rview.center.x > view.bounds.size.width
animateLeftPanel(shouldExpand: hasMovedGreaterThanHalfway)
}
default:
break
}
}
This is a common issue - the map view will 'consume' all touch events, meaning your gesture recognizer will never be triggered.
The solution is to use a UIScreenEdgePanGestureRecognizer, the full solution is detailed below:
https://stackoverflow.com/a/24887059
Related
I'm trying to add a shortcut item to my app. I have the item appearing, and it's responding but can't work out how to get the segue to work. I have it going to the right tab but need to go to the root view and perform a segue from there.
The segue is already setup on the ProjectList view controller and its called "addProject"
My view storyboard is setup as so:
UITabViewController -> UINavigationController-> UITableViewController (ProjectList) -> Other additional views
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "addProject" {
if let window = self.window, let tabBar : UITabBarController = window.rootViewController as? UITabBarController {
tabBar.selectedIndex = 0
}
}
shortcutItemToProcess = nil
}
}
Solved with
func applicationDidBecomeActive(_ application: UIApplication) {
if let shortcutItem = shortcutItemToProcess {
if shortcutItem.type == "addProject" {
guard let window = self.window else { return }
guard let tabBar = window.rootViewController as? UITabBarController else { return }
guard let navCon = tabBar.viewControllers?[0] as? UINavigationController else { return }
guard let projectList = navCon.rootViewController as? ProjectList else { return }
projectList.performSegue(withIdentifier: "addProject", sender: nil)
tabBar.selectedIndex = 0
}
shortcutItemToProcess = nil
}
}
I made a side menu with some controls and I can dismiss it when the user taps outside of the side menu or if he/she selects a row inside the side menu. Now I want to add a swipe gesture to the left so that the user can dismiss it that way too.
extension MenuViewController {
#objc func dismissControllerAnimated() {
dismiss(animated: true, completion: nil)
} }
class SlideinTransition: NSObject, UIViewControllerAnimatedTransitioning {
let menuViewController = MenuViewController()
var isPresenting = true
let dimmingView = UIView()
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController(forKey: .to),
let fromViewController = transitionContext.viewController(forKey: .from) else { return }
let containerView = transitionContext.containerView
let finalWidth = toViewController.view.bounds.width * 0.3
let finalHeight = toViewController.view.bounds.height
if isPresenting{
//adds a tap gesture to our dimming view
let tapGesture = UITapGestureRecognizer(target: toViewController, action: #selector(MenuViewController.dismissControllerAnimated))
dimmingView.addGestureRecognizer(tapGesture)
//adds the dimming view
dimmingView.backgroundColor = .black
dimmingView.alpha = 0.0
containerView.addSubview(dimmingView)
dimmingView.frame = containerView.bounds
//adds the menu view controller to our container
containerView.addSubview(toViewController.view)
//init frame off the screen
toViewController.view.frame = CGRect(x: -finalWidth, y: 0, width: finalWidth, height: finalHeight)
}
let transform = {
self.dimmingView.alpha = 0.5
toViewController.view.transform = CGAffineTransform(translationX: finalWidth, y: 0)
}
//applies a specific kind of transformation to our view
let identity = {
self.dimmingView.alpha = 0.0
fromViewController.view.transform = .identity
}
//animates the transition and cancels it when you click outside of the frame
let duration = transitionDuration(using: transitionContext)
let isCancelled = transitionContext.transitionWasCancelled
UIView.animate(withDuration: duration, animations: {
self.isPresenting ? transform() : identity()
}) { (_) in
transitionContext.completeTransition(!isCancelled)
if !self.isPresenting {
self.dimmingView.removeFromSuperview()
}
}
}
}
I instantiate my "MenuViewController" like this into my MainController
#IBAction func didTapMenu(_ sender: UIBarButtonItem) {
guard let menuViewController = storyboard?.instantiateViewController(withIdentifier: "MenuViewController") as? MenuViewController else { return }
menuViewController.didTapMenuType = { menuType in
self.transitionToNew(menuType)
}
menuViewController.modalPresentationStyle = .overCurrentContext
menuViewController.transitioningDelegate = self
present(menuViewController, animated: true)
}
How can I add a SwipeGestureRecognizer to my transition? I appreciate every input. Thanks in advance!
view.bringSubviewToFront(tappedView)
is working when I drag (pan) views but not when I tap them. My issue is that when I have one view layered over the other, I want the bottom view to come to the front when tapped. Currently it will only come to the front when dragged. Do I need some additional code to do this? Thanks.
Here's an excerpt from my code for more context:
#objc func didPan(_ recognizer: UIPanGestureRecognizer) {
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .began:
currentTappedView = moviePieceView.filter { pieceView -> Bool in
let convertedLocation = view.convert(location, to: pieceView)
return pieceView.point(inside: convertedLocation, with: nil)
}.first
currentTargetView = movieTargetView.filter { $0.pieceView == currentTappedView }.first
case .changed:
guard let tappedView = currentTappedView else { return }
let translation = recognizer.translation(in: self.view)
tappedView.center = CGPoint(x: tappedView.center.x + translation.x, y: tappedView.center.y + translation.y)
recognizer.setTranslation(.zero, in: view)
view.bringSubviewToFront(tappedView)
```
I had this same problem, I managed to solve it this way:
Add a gestureRecognizer to your view and put this in your code. Don't forget to set the delegate as well when attaching this to the code!
#IBAction func tappedView1(recognizer: UITapGestureRecognizer) {
view.bringSubviewToFront(myView)
}
Put this in your viewDidLoad:
let tap = UITapGestureRecognizer(target: self, action: #selector(tappedView1))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
If you want to do that from your panGesture check my question.
Thank you all. It worked when I wrote a second function in addition to didPan().
This is the additional code:
override func viewDidLoad() {
super.viewDidLoad()
configureLabel()
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(_:)))
view.addGestureRecognizer(panGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func didTap(_ recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: self.view)
currentTappedView = moviePieceView.filter { pieceView -> Bool in
let convertedLocation = view.convert(location, to: pieceView)
return pieceView.point(inside: convertedLocation, with: nil)
}.first
guard let tappedView = currentTappedView else { return }
view.bringSubviewToFront(tappedView)
}
HOWEVER, I found out you can also just tick the "User Interaction Enabled" box in the Image View if you don't want to do it programmatically.
I've got a Swift function that uses a switch statement to act on various enum cases but I also need to check for another condition at the same time. The best solution I can come up with is to use a nested switch (which works) but I was wondering if there was a more elegant (Swifty?) way?
The code is pretty self-explanatory:
func transitionTo(scene: Scene, transition: SceneTransitionType) -> Observable<Void> {
let subject = PublishSubject<Void>()
let viewController = scene.viewController()
switch viewController {
case is UISplitViewController:
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
default:
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
}
default:
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
case .push(let animated):
guard let nc = currentViewController as? UINavigationController else {
fatalError("Unab;e to push a view controlled without an existing navigation controller")
}
_ = nc.rx.delegate // one-off sub to be notified when push complete
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
nc.pushViewController(viewController, animated: animated)
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
case .modal(let animated):
currentViewController.present(viewController, animated: animated) {
subject.onCompleted()
}
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
}
This is the best possible way as per my knowledge with single switch statement.
func transitionTo(scene: Scene, transition: SceneTransitionType) -> Observable<Void> {
let subject = PublishSubject<Void>()
let viewController = scene.viewController()
switch transition {
case .root:
window.rootViewController = viewController
subject.onCompleted()
case .push(let animated):
if viewController is UISplitViewController {
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
return
}
guard let nc = currentViewController as? UINavigationController else {
fatalError("Unab;e to push a view controlled without an existing navigation controller")
}
_ = nc.rx.delegate // one-off sub to be notified when push complete
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
nc.pushViewController(viewController, animated: animated)
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
case .modal(let animated):
if viewController is UISplitViewController {
fatalError("UISplitViewController can only be exist at the root of the view hierachy")
return
}
currentViewController.present(viewController, animated: animated) {
subject.onCompleted()
}
currentViewController = SceneCoordinator.topViewControllerInStackWith(root: viewController).first!
}
}
I have an NSTabViewController, and I want to create some custom transition.
I added some NSViewControllerTransitionOptions values and when transition method is called with my values, a custom animation should run.
Bellow is the intermediate code that I written until now. Animation run exactly how what I want, but there is a problem.
nextVC is not presented (I think). That controller should be first responder, after animation that is not respond to keyboard import.
override func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewControllerTransitionOptions = [], completionHandler completion: (() -> Void)? = nil) {
if options.contains(.analogToThemes) {
if let firstVC = fromViewController as? MBCustomizeController {
let nextVC = toViewController
let themesContainer = nextVC.view
themesContainer.setFrameOrigin(NSMakePoint(-250, -510))
var sketchContainer:NSView?
var panelsContainer:NSView?
firstVC.view.addSubview(themesContainer)
for item in firstVC.view.subviews {
if item.identifier == "sketchContainer" {
sketchContainer = item
}
if item.identifier == "customizePanlesContainer"{
panelsContainer = item
}
}
NSAnimationContext.runAnimationGroup({ context in
context.duration = animationDuration
themesContainer.animator().setFrameOrigin(NSMakePoint(0, 0))
sketchContainer!.animator().setFrameOrigin(NSMakePoint(sketchContainer!.frame.origin.x, 520))
panelsContainer!.animator().setFrameOrigin(NSMakePoint(panelsContainer!.frame.origin.x + panelsContainer!.frame.width , 0))
}, completionHandler: {
firstVC.dismiss(nil)
})
}
return
}
super.transition(from: fromViewController, to: toViewController, options: options, completionHandler: completion)
}
How can I present nextVC correctly?
Thanks.