How can I present a custom view modally without using presentViewController? - ios

Is it possible to present a view controller modally using addChildViewController? I want to present a UIviewcontroller's view modally so it overlaps any views under the window hierarchy. I want to do it without using the self.presentViewcontroller because I want to build my own animation instead of using the preset flip up/down/ etc. I tried using the self.addChildViewcontroller(modal) and self.view.addSubView(modal.view) but it goes inside my ChildViewController. When I used self.view.window?.addSubview, the app crashes.
Is it possible to present a UIViewcontroller modally using addChildController?
I included my sample codes.
https://github.com/cuongta/testcode
override func viewDidLoad() {
super.viewDidLoad()
var childVC = ChildViewController()
var navVC = UINavigationController()
navVC.viewControllers = [childVC]
self.addChildViewController(navVC)
self.view.addSubview(navVC.view)
}

You should use UIViewControllerAnimatedTransitioning and UIViewControllerTransitioningDelegate to achieve what you want.
I think it's the best way according to Apple.
Adopt the UIViewControllerAnimatedTransitioning protocol in objects
that implement the animations for a custom view controller transition.
The methods in this protocol let you define an animator object, which
creates the animations for transitioning a view controller on or off
screen in a fixed amount of time. The animations you create using this
protocol must not be interactive. To create interactive transitions,
you must combine your animator object with another object that
controls the timing of your animations.
You can download an Apple Sample Code here

Related

Convert modal view to fullscreen view

I have a modal view controller A presented at the top of the view controllers hierarchy.
Is it possible to make it fullscreen temporarily when a user presses a button? And then go back to standard modal look when used presses another button?
To clarify, I'm not talking about modalPresentationStyle = .fullScreen BEFORE A is presented, I need to present A in a standard way and then stretch it to be fullscreen when needed.
Although if I write pseudo code it would be something like this:
class A: UIViewController {
#objc func goFullScreen(sender: UIButton) {
// pseudo code:
// modalPresentationStyle = .fullScreen
// go fullscreen, block `drag-to-dismiss` gesture, remove `cornerRadius`, set `self.view.frame = UIScreen.main.bounds`
}
#objc func cancelFullScreen(sender: UIButton) {
// pseudo code:
// modalPresentationStyle = .pageSheet
// return to the standard look of a modal view controller
}
}
I found UIAdaptivePresentationControllerDelegate.presentationControllerShouldDismiss method that intercepts drag to close gesture, but I don't see any way to go fullscreen.
Picture of what I am talking about:
ps. I know I can make a custom container view controller with a custom transition delegate but I'd like to know if it's possible to implement such behaviour with system classes because I basically need just to flip modalPresentationStyle property with some interpolation animation...
Also on the right I hide/show some UI elements, but still it's the same view controller

Fixed Navigation Bar For Different View Controllers

I am trying to implement a navigation bar whose contents persist between different view controllers. For example, I have the following functionality right now:
Non Persistent Navigation Bar
I have set an imageView as the titleView of the navigation bar.
The titleView of the navigation bar transitions along with the view controller here (the image shows some animations by fading in and out). But I would like it to stay hooked onto the top of every screen without any transitions. This would mean that only the part of the view below the navigation bar would show the transition from one view controller to another.
Is that possible in Swift?
Yea that is possible. What you can do is have a container view controller, which can have your navigation bar along with a content view controller.
Now each time you open a new VC, push the new VC on the containerVC's contentVC.
For ex:
let containerVC = self.parentViewController?.containerViewController()
if let _ = containerVC {
containerVC.pushContentViewController(newViewController)
}
Attaching layout screenshot for more understanding.
So if you check here, the Root Container is the view where you can add your new VC as a child VC.
You can do this by changing your UIViewController hierarchy. For this you'll need three view controllers. First will own your UINavigationBar and UIView where other two UIViewController's views will live.
Let's call one with the navigation bar MasterViewController, and other two—ViewControllerA, ViewControllerB respectively.
Somewhere in MasterViewController instantiate child view controllers and add them to your view controller hierarchy. For simplicity's sake let's do everything in viewDidLoad() but you can do do this anywhere you deem it necessary. You could even load view controllers lazily as user demands them.
final class ViewControllerA: UIViewController { }
final class ViewControllerB: UIViewController { }
final class MasterViewController: UIViewController {
var navigationBar = UINavigationBar()
override func viewDidLoad() {
view.addSubview(navigationBar)
let a = ViewControllerA()
let b = ViewControllerB()
addChildViewController(a)
addChildViewController(b)
view.addSubview(a.view)
// you are ready for transitions from a.view to b.view when necessary
}
}
Now you can do transitions from a.view to b.view (and back) and it will affect nothing in master view (which has the navigationBar).
It is important to note that view hierarchy and view controller hierarchy are not liked in any way and you are responsible for managing both.

How to swipe a View controller over the top of another view controller?

I have a table view controller and another view controller. Now my requirement is that i need to swipe the table view controller half over the another view controller when i swipe the view controller. The image i can show is like this:
Is it possible to achieve this by using the Swipegesture . If possible how can i do this in Swift3?
While there are libraries out there to do this for you, if you are going to do this yourself, the basic idea is that you can create a "swipe from edge" gesture recognizer to initiate a custom presentation of a view controller (that has your menu on it). It consists of:
a custom transitioning delegate (conforming to UIViewControllerTransitioningDelegate protocol) that specifies the animation controller and interaction controller (both described below) to be used during the presentation of the next scene;
an animation controller (conforming to UIViewControllerAnimatedTransitioning) that dictates the animation of the scene from the right edge;
an interaction controller (a UIPercentDrivenInteractiveTransition) that you can use to optionally drive the transition via a gesture;
a presentation controller (a UIPresentationController subclass) that dictates whether the presented view will be removed or not (in this case you do not want it removed), as well as any other chrome to be animated alongside the animated presentation of the new scene (e.g. you often dim or blur the presenting view); and
gesture recognizers to drive the interaction controller.
For more information, see WWDC videos Custom Transitions Using View Controllers and A Look Inside Presentation Controllers.
See https://github.com/robertmryan/SwiftCustomTransitions/tree/rightside for a Swift 3 example that renders the following UX:
This is all admittedly complicated enough that you may well want to consider third party libraries for presenting slide-in menus. But if you wanted to "roll your own", these are the basic pieces involved.
What I would do is first create and hide a UIView that lets you select those UIViewController on the right side of the screen and animate it to show when the user swipes.
Then you implement this method that returns UIViewControllerAnimatedTransitioning, a class that you want to implement for custom transitioning.
- (id<UIViewControllerAnimatedTransitioning>)
navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
This could be a good tutorial for custom transitioning.
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions
I would look into SWRevealViewController. It gives you the behavior you are looking for in just a few lines of code. You can present an SWRevealViewController that will hold your top and bottom UIViewController and present that as your scene.
// Your Front View Controller
let frontStoryboard = UIStoryboard(name: "FrontStoryboard", bundle: .main)
let frontVC = frontStoryboard.instantiateInitialViewController()
// Your Rear View Controller
let rearStoryboard = UIStoryboard(name: "RearStoryboard", bundle: .main)
let rearVC = rearStoryboard.instantiateInitialViewController()
// Create Reveal View Controller From Both
if let revealVC = SWRevealViewController(rearViewController: rearVC, frontViewController: frontVC) {
present(revealVC, animated: true) {
}
}
Using SWRevealViewController you can set the UIViewController that you are trying to do the sliding in as the frontViewController and you can present the rearViewController simply using a single line of code.
// When you import SWRevealViewController every UIViewController
// has a method revealViewController() that returns the revealViewController
// that you can tell to toggle it's reveal state using the below
self.revealViewController().revealToggle(animated: true)
EDIT
I believe you are trying to build a sliding navigation menu. Here is a link to a Ray Wenderlich tutorial where he is creating exactly the navigation you are looking for.

Insert subview behind TabBar from child view of TabBarController

I have TabBar with 2 tabs. At some point, from either of the 2 tabs, I want to add a view that is visible on both tab views but behind the TabBar.
So I thought, insert a subview into the TabBarController but below the TabBar.
This works fine in principle and I have the view behind the TabBar but now covering my 2 tabs as I wanted. However, it doesn't actually load. Just its background loads and only viewDidLoad() is called, not viewWillAppear() or any others.
I have also tried calling addChildViewController(myVC) on the TabBarController which has no effect, and also manually calling viewWillAppear() on the view controller I add which also has no effect (and I'm also dubious about whether manually calling viewWillAppear() is permitted or not?).
Is what I'm trying to do possible? What am I missing? Or should I be attempting this some other way?
For some reason, when inserting a subview into a UITabBarController behind it's UITabBar, although the view is visible to the user, the system itself seems to think it is not and so although viewDidLoad() is called, viewDidAppear() and subsequent methods are not.
However, adding a subview above the UITabBar seems to work fine. So I solved this by adding my own new UITabBar as a subview to the UITabBarController (set up basically exactly as the default one would be) and then removing the UITabBarController's default UITabBar.
Then when later inserting my view into the UITabBarController, I insert it as I was doing originally but instead below/behind my custom UITabBar and it seems to load fine.
There is no need to remove and recreate the tabBar. What you need to do is after you insert your custom view, you can then bring the tabBar to the front again.
//bring the tabBar to the front after inserting new view
self.view.bringSubview(toFront: self.tabBar)
This would be a good way:
Add the function below and call it in viewDidLoad of your initial VC. It unwraps your tab bar controller instance (which is optional), and then inserts the view you always want visible just below the tab bar.
private func setupAlwaysVisibleView() {
guard let tabBarController = self.tabBarController else { return }
tabBarController.view.insertSubview(alwaysVisibleView, belowSubview: tabBarController.tabBar)
}
Avoid using optionals for tabBarController or removing current tabBar. Simple add your view below tabBar view. Swift 5, XCode 11.
class TabBarController: UITabBarController {
#IBOutlet var instructionsView: UIView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.insertSubview(instructionsView, belowSubview: self.tabBar)
}
}
you can also do this inside the init() method for your UITabViewController:
view.insertSubview(alwaysVisibleView, belowSubview: self.tabBar)
no need to dispatch to another method if you are using a subclass of UITabViewController.

In iOS 7, with customer VC transitioning, is it possible to make "fromviewcontroller" on screen after presentViewController?

Many apps have the feature which, user can pull down one view, and another view shows up from underneath it. But the first view is still visible on the bottom. (e.g. Facebook Paper App).
If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
Anyone more familiar with this ? Thanks !
If the other view is from another view controller, is it possible to achieve this by using iOS 7's custom view controller transitioning API ?
Yes, it's possible. You can use the snapshot API to take a snapshot of any UIView. With this you can take a snapshot of view controller you're transitioning from, then add it to the containerView below the view controller you're transitioning to.
For example, in the -animateTransition: method of your class that adopts UIViewControllerAnimatedTransitioning take a snapshot of the view controller you're transitioning from and add it as a subview beneath the view controller you're transitioning to:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
UIView * containerView = transitionContext.containerView;
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// take snapshot of from view controller
UIView * fromSnapshotView = [fromViewController.view snapshotViewAfterScreenUpdates:NO];
[containerView insertSubview:fromSnapshotView belowSubview:toViewController.view];
// Then do your animations on the to view controller to animate it into view as well as the fromSnapshotView
// Finally, don't forget to call:
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}
In my test, it is possible to do the "presenting" part, but the "dismissing" part has a glitch. Whenever we call dismissViewController, the "toViewController" takes over the full screen even before calling the transitioningDelegate methods.
If your call to dismiss doesn't fire your transitioning delegate methods make sure that you set the transitioning delegate on that view controller before dismissing.

Resources