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!
}
}
Related
i want to get topMostVC so i did this function:
func topMostViewController() -> UIViewController {
if let presented = self.presentedViewController {
return presented.topMostViewController()
}
if let navigation = self as? UINavigationController {
return navigation.visibleViewController?.topMostViewController() ?? navigation
}
if let tab = self as? UITabBarController {
return tab.selectedViewController?.topMostViewController() ?? tab
}
return self
}
Then called using:
AppDelegate.shared.window?.rootViewController?.topMostViewController()
But i have problem is when UIAlertController is showing in screen, it's return UIAlertController, not my expect viewcontroller
Can anyone tech me how to avoid UIAlertController, thanks
Just add one more condition
if let presented = self.presentedViewController,
!(presented is UIAlertController) { // <== Here or use !presented.isKind(of: UIAlertController.self)
return presented.topMostViewController()
}
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
}
}
Suppose I am currently on BaseViewController . I can approach this view controller class from 2 other view controllers, say A and B. So when I pop BaseViewController how do I check if A or B is present on top of the stack? Here is my code below(inside BaseViewController):
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
// Id after popping viewController is B do this
// Instantiate B and push it
}
There are many methods to do this
First and easiest method
func visibleViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}
func goToAOrBViewController {
// If after popping viewController is A do this
navigationController.popViewController(animated: true)
if visibleViewController() == viewControllerA{
//Do stuff for A
}else{
//Do stuff for B
}
}
Second and obvious method
let topController = UIApplication.sharedApplication().keyWindow?.rootViewController
if topController == viewControllerA {
//Do stuff for A
}else{
//Do stuff for B
}
You can pop and push view controller at the same time.
not tested:
navigationController?.viewControllers.removeLast()
let topVC = navigationController?.viewControllers.last
if topVC == A {
navigationController?.viewControllers.append(A)
navigationController?.setViewControllers(A, animated: false)
} else {
navigationController?.viewControllers.append(B)
navigationController?.setViewControllers(B, animated: false)
}
Inside BaseViewController class:
func goToAOrBViewController() {
guard let navigationController = self.navigationController else { return }
navigationController.popViewController(animated: false)
guard let viewController = navigationController.topViewController else { return }
if viewController is ViewControllerA {
// Do something for A
} else if viewController is ViewControllerB {
// Do Something for B
}
// You need not put else-if case if you are sure BaseVC is reachable from A and B only (just use else)
}
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
I have a PhoneViewController : UIViewController
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
I am presenting it from 2 different controllers.
// UIViewController1
self.navigationController?.pushViewController(phonePage, animated: true)
// UIViewController2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
I would like to have a have to detect which controller was its parent. How would I be able to do that?
you can try this code:-
if let wd = self.window {
var vc = wd.rootViewController
if(vc is UINavigationController){
vc = (vc as UINavigationController).visibleViewController
}
if(vc is YourViewController){
//your code
}
}
The short answer is :
if let navController = self.navigationController {
return navController.viewControllers[navController.viewControllers.count - 1]
// take care if count <= 1
else {
return self.parent
}
But is this what you really looking for ? What behavior are you trying to implement based on who is his parent ?
I don't have the answer to this question but you should make your code readable. Let me explain by an example :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
phonePage.mode = PhonePageMode.list
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
phonePage.mode = PhonePageMode.grid
// In your PhoneViewController class
switch self.mode {
case .list: // present as a list
case .grid: // present as a grid
}
is more readable than :
let phonePage = storyboard.instantiateViewControllerWithIdentifier("phoneViewController") as! PhoneViewController
// Option 1
self.navigationController?.pushViewController(phonePage, animated: true)
// Option 2
self.presentViewController(phonePage.embedInNavController(), animated: true, completion: nil)
// In your PhoneViewController class
guard let parent = self.parentViewController else { return }
if parent is ThisClassWhichWantsAList {
// present as list
} else if parent is ThisOtherClassWhichWantsAGrid {
// present as grid
}
If you want to use its parent as a condition to do things differently, you'd rather use an additional attribute. Your futur self will be thankful.
if your'r view controller is pushed from navigationViewController than parent class in parentViewController available and if presented than in presentationController available.
You can check in this way:
if let parentVC = self.navigationController.parentViewController{
//parentVC.className
}
if let presentedVC = self.navigationController?.presentationController{
//presentedVC.className
}
So you basically have to check if the view controller in the navigation controller stack, at the index of count - 1 is the kind of view controller that you are looking for.
Short version:
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: TheVCYouAreLooking for.self) {
print("it is")
} else {
print("it is NOT")
}
Long version from a playground:
//These are your two view controllers
class FirstVCClass: UIViewController {}
class SecondVCClass: UIViewController {}
let vc = FirstVCClass()
let secondVC = SecondVCClass()
//Create a navigationcontroller and add the first VC in the stack
let navigationController = UINavigationController()
navigationController.setViewControllers([vc], animated: true)
//Now push the secondVC
vc.navigationController?.pushViewController(secondVC, animated: true)
//The last vc in the stack is the one you've just pushed
print(navigationController.viewControllers.last!)
// Now check
if navigationController.viewControllers[navigationController.viewControllers.count - 1].isKind(of: FirstVCClass.self) {
//The view controller that is before you current vc in the stack is of the class
print("it is")
} else {
print("it is NOT")
}