I'm currently learning the very basics of Swift, having just finished the iOS Application Development Tutorial from the Apple Developer website.
I am wanting to create a swipe gesture that's very similar to most popular applications that are out at the moment. Swipe right to reveal the content from another view from the left, essentially a 'back' button but with a gesture instead.
Here is an image I found of the gesture I want to create:
The tutorial from Apple explains how the cancel button works in the navigation controller:
//MARK: Navigation
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController {
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
And so (referencing the tutorial from Apple) I placed a Swipe Gesture Recogniser object on the 'New Meal' controller and created my own 'rightSwipe' function that does the exact same thing:
#IBAction func rightSwipe(_ sender: UISwipeGestureRecognizer) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController {
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
This works great, the application at the moment is clever enough to recognise your 'entry' to the view (whether it's via tapping on an existing item on the list or via the 'create a new item' view).
Is it now possible to control the animation with your finger instead of executing the entire thing as soon as the screen has been swiped to the right? This is a little difficult to explain...
As soon as I swipe right, the animation gets executed and the current view is dismissed. I have no control over the animation until it is over. I'm not able to animate the 'dismissal' with my finger as I drag it across the screen.
The scenario I want to explain is very similar to how switching pages on the home screen of iOS works.
I want to be able to 'hold' the screen on the left, be able to drag it to the right, while holding down, and should be able to see the previous view as I do it, then execute the actual transition of views as soon as I let go. I hope you understand what I mean?
How can this be achieved? Is there anything currently built in that allows me to do this? Would anyone be able to show me how this can be done?
I'm currently working with Swift 3 and using the Xcode9-beta IDE
Related
I'm creating an application where I place a view controller ontop of another with
Presentation: current context
But when I'm trying to dismiss the screen by dragging the top towards the bottom the view does not disappear.
I've also tried to add a button to make it dismiss but that doesn't work either.
#IBAction func dis(_ sender: Any) {
navigationController?.popViewController(animated: true)
self.dismiss(animated: true)
}
So, how can I dismiss both views when dragging the top one down when it uses "current context" as presentation style?
I'm using "current context" because the previous screen should never be displayed again. And instead of dragging down both, I would like to drag down just one to make both of them disappear. But it does not seem to work as expected.
Although "current context" is not for this purpose as #matt mentioned,
You should dismiss the controller who presents this one to dismiss both together in this case:
self.presentingViewController!.dismiss(animated: true) // `!` is to makeing sure it's not crashing.
Demo:
Use this simple code to demo:
class ViewController: UIViewController {
#IBAction func present() {
let destination = storyboard!.instantiateInitialViewController()!
if presentingViewController != nil {
// You are not in the A
if presentingViewController?.presentingViewController != nil {
// You are in the C
presentingViewController?.presentingViewController?.dismiss(animated: true)
return
} else {
// You are in the B
destination.modalPresentationStyle = .currentContext
}
}
present(destination, animated: true, completion: nil)
}
}
Usage:
Create a single view application
Drag a button into the view controller
Replace the ViewController class with the provided code
Connect the #IBAction
Run it to see the result.
If you use fullscreen presentation (so that there is no drag-to-dismiss) it's quite simple to do this with essentially no code at all.
Note that the yellow view controller appears as an intermediary when presenting, but not when dismissing.
I have a Tab bar in the first view and a total of two views.
However, when I move from the first view to the second view and then back to the first view using the Segue, the Tab bar of the first view disappears.
When return to the first view from the second view, what is the way the Tab bar does not disappear?(without using unwind)
Don't use unwind segue here. When you need to get back to previous ViewController, just dismiss your current ViewController
dismiss(animated: true, completion: nil) /* call this in second VC */
I suggest you read the documentation for understanding how combined View Controller Interfaces. Anyway, if you need to pop to previous view controller on the flow, you need to use
navController.popViewController(animated: true)
But, if the need to pop up on the specific ViewController on the queue of View Controllers in the NavigationViewController, you need to use
navController.popToViewController(ViewController, animated: true)
From the moment you are using a NavigationController, a return button will automatically appear on the UINavigationBar, so you do not have to worry about that. Unless you want to customize the back buttons in the viewcontrollers queue, in this case use the above methods.
It's for page 1. Display tabBarController once page is loaded.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.tabBar.hidden = true
}
If click event is fired, hide tabBarController.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "twoSegue") {
self.tabBarController?.tabBar.hidden = false
}
}
Within my app, I want to create a user profile controller, which can be navigated to from several different controllers. I would like it to have a back button that will take the user back to whichever controller they came from.
How do I do this?
Dismiss
Add UIButton for back navigation somewhere to your UserProfileController. Then in UserProfileController create action and set it as action of your button. This action dismisses your UserProfileController (so you get back to previous UIViewController)
#IBAction func backButtonPressed(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
UINavigationController
Alternatively you can have previous ViewControllers embed in UINavigationController. Then you will be able to tap to back button (which is set by default) in UINavigationBar which gets you to previous UIViewController
I have a very specific use case that I am wondering if anyone has dealt with. I want to add a "Continue" button on my first time user experience UIPageViewController. This article (Swift iOS: how to trigger next page using buttons), touches if you want it to be on every page, but I am not planning that broad of use.
I have a UIPageViewController, and 6 viewcontrollers that I currently can navigate through by swiping.
I've tried connecting the button to the view controller as a segue, but that presents it outside of the PageViewController. I've thought of trying to just push a right to left swipe onto the button click, but I couldn't quit figure that out.
Anyone have an idea on how to attack putting a single button on one of the view controllers and have it progress to the next view controller within the UIPageViewController?
This was marked as duplicate, but there are no duplicate cases of this issue on SO. There are few similar, but nothing like it. If there is one, please link it. The currently linked version is an obj-c answer to a similar question with no explanation as to how to link the needed function to the view controller. That answer I can implement as a global button easily, but a single view controllers button is not covered by it.
Thanks
Edit for final code solution.
So this was a two part solution coded in swift 3.
In the uipageviewcontroller data source extension, the func to flip the page was built as follows.
func goNextPage() {
let cur = self.viewControllers![0]
let p = self.pageViewController(self, viewControllerAfter: cur)
self.setViewControllers([p!], direction .forward, animated: true, completion: nil)
}
The piece in the individual view controller is built as follows
#IBAction func nextPageButtonWasTapped(_ sender: Any) {
var candidate: UIViewController = self
while true {
if let pageViewController = candidate as? MyUIPageViewController {
pageViewController.goNextPage()
break
}
guard let next = parent else { break }
candidate = next
}
}
Walk up the view controller hierarchy to find the page view controller. When you find it, ask it to turn the page:
#IBAction func nextPageButtonWasTapped(_ sender: AnyObject?) {
var candidate: UIViewController = self
while true {
if let pageViewController = candidate as? UIPageViewController {
candidate.navigateForward()
break
}
guard let next = parentViewController else { break }
candidate = next
}
}
Implement navigateForward in an extension on UIPageViewController, based on the Objective-C version from this answer.
I'm trying to use MMDrawerController in my application, but I can't get the side drawer to close with gestures on the center view. I've used MMDrawerController somewhere else in my app with great success, but can't figure out why its not working in this case.
The top-level View Controller is a UINavigationController, whose default view controller is the MasterViewController (source below). This class extends MMDrawerController and configures the view for what I want (center and right, close gestures, max width). The center view has a button that opens the drawer. Once the drawer is open I can't close it with gestures on the center view. I added a button to the drawer and it can close the drawer programmatically, but I need to be able to tab/pan on the center view.
class MasterViewController: MMDrawerController {
override func viewDidLoad() {
let centerView = storyboard!.instantiateViewControllerWithIdentifier("CenterControllerName") as? CenterControllerType
super.setCenterViewController(centerView, withCloseAnimation: false, completion: nil)
let drawer = storyboard!.instantiateViewControllerWithIdentifier("Drawer") as? DrawerType
super.rightDrawerViewController = drawer
super.setMaximumRightDrawerWidth(200, animated: true, completion: nil)
super.closeDrawerGestureModeMask = MMCloseDrawerGestureMode.PanningCenterView | MMCloseDrawerGestureMode.TapCenterView
}
}
The function to open the drawer:
#IBAction func drawerButtonPressed(sender: AnyObject) {
drawer?.openDrawerSide(MMDrawerSide.Right, animated: true, completion: nil)
}
I was able to work around this by placing a ContainerView as the only object in my view and then configuring the MMDrawerContainer from my ViewController. It doesn't seem like the right answer but everything looks and functions right from the user's perspective.
Ok so I run into the same issue, and I am almost sure that it comes from this:
Ok, the VC1 (with the star) is our application Home, the VC2 is the left drawer, and the VC3 is the DrawerController.
During a long time I tried to put gesture functions in the VC1 and VC2, but only opening in the VC1, and closing in the VC2 would work.
The functions I used in VC1:
let rightSwipe = UISwipeGestureRecognizer(target: self, action: Selector("showDrawer:"))
rightSwipe.direction = .Right
view.addGestureRecognizer(rightSwipe)
...
func showDrawer(sender: UISwipeGestureRecognizer) {
if let m = mmdc {
if !m.isLeftDrawerOpen() {
m.toggleDrawerSide(MMDrawerSide.Left, animated: true, completion: nil)
}
}
}
And the same one in VC2 but with the LeftSwipe and closeDrawer.
The solution is tu put both of these functions and gestures recognizer in the VC3 (DrawerController).
The problem is coming from the fact that your gestures are defined for a given VC, but when you open the drawer, it changes the current VC by a new one, and the previous VC is just displayed but can't be interactif with. By putting things in the parentVC of VC1/VC2 it solves the problem.