So, I've made a custom bottom bar with UIButton's. Now I am trying to replicate the fade in transition so when you click one of the buttons from the bottom bar will send to another view controller and show that view. I managed to do that but my navigation bar it's not a custom one and whenever I switch the views, the main view goes under the navigation bar.
I am using this code:
//MARK: Main view
func yourFoodGoalAction() -> Bool {
print("transition: YourFoodGoal")
let nextViewController = YourFoodGoal()
let toView = nextViewController.view
UIView.transition(from: view!, to: toView!, duration: 0.3, options: [.transitionCrossDissolve]) { (true) in
print("disolve")
UIView.animate(withDuration: 0.3, animations: {
self.navigationItem.title = "Your Goal"
})
}
return true
}
And to go back to the view (vice versa):
func selectedBarButtonAction() -> Bool {
print("transition: YourFoodGoal")
let nextViewController = YourFoodVC()
let toView = nextViewController.view
UIView.transition(from: view!, to: toView!, duration: 0.3, options: [.transitionCrossDissolve]) { (true) in
UIView.animate(withDuration: 0.3, animations: {
self.navigationItem.title = "Today's Meals"
})
}
return true
}
What should I do so my views will not go under the navigation bar once I change between them? I do not want to use container view as every view will be custom. Everything it's being made programmatically.
I found my answer. I was calling the modal transition wrong. Modals go full screen with the main view. I just embedded the navigation when I am presenting the new view and call the modal from there:
func todaysMealsAction() {
let yourFoodVC = YourFoodVC()
yourFoodVC.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
let navBarOnModal: UINavigationController = UINavigationController(rootViewController: yourFoodVC)
self.present(navBarOnModal, animated: false, completion: nil)
}
Related
So I have 2 View Controllers and a Navigation Controller. When the screen is clicked, the 1st VC segues to the 2nd VC, and there is a back button for the segue to unwind to go back to the 1st VC.
I did not like the vertical animation of the segues, so (with some help) I created custom, horizontal animations.
The 1st VC works great with the animation sliding from right to left. But once that is done, the 2nd VC does not want to unwind (2nd VC should go from left to right).
I do get this Warning..
Warning: Attempt to present UINavigationController: 0x7fce2082b000 on TestApp.HomeViewController: 0x7fce20410030 whose view is not in the window hierarchy!
Also, if I take the script from the 1st VC segue, then I am able go unwind from the 2nd VC w/ the proper animation.
Here's the code for the segues:
1st VC
#IBAction func performSegue(_ sender: Any) {
if shouldPerformSegue(withIdentifier: "segue", sender: Any?.self) {
performSegue(withIdentifier: "segue", sender: nil)
}
}
#IBAction func unwindToHomeView(segue: UIStoryboardSegue) {
}
override func unwind(for unwindSegue: UIStoryboardSegue, towardsViewController subsequentVC: UIViewController) {
let segue = SegueFromLeft(identifier: unwindSegue.identifier, source: unwindSegue.source, destination: unwindSegue.destination)
segue.perform()
}
Right to Left Animation
let dst = self.destination
let src = self.source
let containerView = src.view.superview
dst.view.transform = CGAffineTransform(translationX: src.view.frame.size.width, y: 0)
containerView?.addSubview(dst.view)
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
dst.view.transform = CGAffineTransform(translationX: 0, y: 0)
}, completion: { success in
src.present(dst, animated: false, completion: nil)
})
Left to Right Animation
let dst = self.destination
let src = self.source
src.view.superview?.insertSubview(dst.view, at: 0)
UIView.animate(withDuration: 0.25, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
src.view.transform = CGAffineTransform(translationX: -src.view.frame.size.width, y: 0)
}, completion: { success in
src.dismiss(animated: false, completion: nil)
})
Any help would be greatly appreciated.
You can simply push viewcontroller to navigation controllers where you don't need to explicitly handle left right animation. In VC1 navigationController?.pushViewController(VC2, animated: true) will simply animate VC2 from right to left with handy back button which can be used to animate VC2 left to right displaying VC1.
For my solution I actually did away with any "custom" animations, and fixed my issues mostly just by moving the navigation controller as the initial controller, attached to the VC1.
From there I just pushed to the next view (VC2), and used unwind segue to go back (to VC1).
Basic code for pushing:
let vcName = "Main"
let viewController = storyboard?.instantiateViewController(withIdentifier: vcName)
self.navigationController?.pushViewController(viewController!, animated: true)
I have two view controllers the first is called "Login View controller" followed by a navigation controller and then another view controller called "Registration View Controller". I found an article online detailing the proper way in performing an unwind segue animation and placed this code inside of "Login View Controller"
override func segueForUnwinding(to toViewController: UIViewController,
from fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return UIStoryboardSegue(identifier: identifier, source: fromViewController, destination: toViewController) {
let fromView = fromViewController.view
let toView = toViewController.view
if let containerView = fromView?.superview {
let initialFrame = fromView?.frame
var offscreenRect = initialFrame
offscreenRect?.origin.x -= (initialFrame?.width)!
toView?.frame = offscreenRect!
containerView.addSubview(toView!)
// Being explicit with the types NSTimeInterval and CGFloat are important
// otherwise the swift compiler will complain
let duration: TimeInterval = 1.0
let delay: TimeInterval = 0.0
let options = UIViewAnimationOptions.curveEaseInOut
let damping: CGFloat = 0.5
let velocity: CGFloat = 4.0
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: damping,
initialSpringVelocity: velocity, options: options, animations: {
toView?.frame = initialFrame!
}, completion: { finished in
toView?.removeFromSuperview()
if let navController = toViewController.navigationController {
navController.popToViewController(toViewController, animated: false)
}
})
}
}
What happens now when I press the back button inside of the "Registration View Controller" is the code successfully executes but then removes the "Login View Controller" from view and stays on the "Registration View Controller". After switching out "toView?.removeFromSuperview()" to "fromView?.removeFromSuperview()", it successfully stays on the "Login View Controller" but now if I press the button to take me to "Register View Controller" it just sends me back to "Login View Controller" essentially creating an infinite loop.
I am making an app that uses a tab bar to navigate between view controllers. I wanted to add a transition effect that would cross dissolve between each view when a tab button was pressed. I have implemented this transition with UIView.transitionFromView, however the navigation bar is not working as expected during the transition. During a transition to a view for the first time, the navigation bar is displayed too high, but jumps back into place once the transitions is complete. However, the next time you switch to the same view, the navigation bar is in the correct place during and after the transition.
I have seen an answer here to fix the problem for a custom animation, but I could not figure out how to get it to work with my current implementation.
MY Question
I have seen answers fixing the issue by forcing the view down by a few points (44 points), but is there a way to do it without directly changing the points? This might work the first time, but the issue resolves itself when any view is transitioned to a second time, thus making the view too low if you change the points.
Here is my code for the tab bar controller and the transition:
import UIKit
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Method used to detect when a tab bar button has been tapped
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
// Creating the 'to' and 'from' views for the transition
let fromView = tabBarController.selectedViewController!.view
let toView = viewController.view
if fromView == toView {
// If views are the same, then don't do a transition
return false
}
self.view.userInteractionEnabled = false
UIView.transitionFromView(fromView, toView: toView, duration: 2.0, options: .TransitionCrossDissolve, completion: nil)
self.view.userInteractionEnabled = true
return true
}
}
And here is what the issue looks like:
You can try with this code:
import UIKit
class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Method used to detect when a tab bar button has been tapped
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
// Creating the 'to' and 'from' views for the transition
let fromView = tabBarController.selectedViewController!.view
let toView = viewController.view
if fromView == toView {
// If views are the same, then don't do a transition
return false
}
self.view.userInteractionEnabled = false
if let window = fromView.window {
let overlayView = UIScreen.mainScreen().snapshotViewAfterScreenUpdates(false)
viewController.view.addSubview(overlayView)
UIView.transitionFromView(fromView, toView: toView, duration: 2.0, options: .TransitionCrossDissolve, completion: { (finish) in
window.rootViewController = viewController
UIView.animateWithDuration(0.4, delay: 0.0, options: .TransitionCrossDissolve, animations: {
overlayView.alpha = 0
}, completion: { (finish) in
overlayView.removeFromSuperview()
})
})
}
self.view.userInteractionEnabled = true
return true
}
}
In my case, call toView.layoutIfNeeded() before the transition fixed the issue.
I'm developing an ios app. I have a a main view and in this view
im trying to present a modal view controller with dimmed background(black with opacity).
The problem is that the status bar is not affected by this color and remains the same.
This is how i present the view controller:
let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController
shareViewController.battle = battle
shareViewController.delegate = self
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.addAnimation(animation, forKey: kCATransition)
presentViewController(shareViewController, animated: false) {
() in
// nothing here
}
Here are some screenshots to demonstrate the problem:
This is the problem(status bar color):
Problem illustration
This is the modal view in storyboard:
storyboard
I cannot reproduce your problem, the following code works without problems in my single view app:
let viewController = UIViewController()
viewController.modalPresentationStyle = .overFullScreen
viewController.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
let animation = CATransition()
animation.duration = 1
animation.type = kCATransitionFade
self.view.window?.layer.add(animation, forKey: kCATransition)
self.present(viewController, animated: false, completion: nil)
However note that you should be presenting over the root controller of the view. Sometimes you can get strange effects when presenting from your internal controllers:
self.view.window?.rootViewController?.present(viewController, animated: false, completion: nil)
Also make sure you are using the correct modalPresentationStyle.
Set your view controller as the root view controller of a UIWindow, then present the window at the UIWindowLevelAlert level.
Below is a Swift 3 class used to animate a modal popup over all other UI elements, including the status bar. A scrim view is used to shade background UI and intercept touches to dismiss the view.
import UIKit
class ModalViewController: UIViewController {
private let scrimView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.black
view.alpha = 0.0
return view
}()
private var myWindow: UIWindow?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear
// Setup scrim View
view.addSubview(scrimView)
view.topAnchor.constraint(equalTo: scrimView.topAnchor).isActive = true
view.leftAnchor.constraint(equalTo: scrimView.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: scrimView.rightAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: scrimView.bottomAnchor).isActive = true
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss as (Void) -> Void))
scrimView.addGestureRecognizer(tapGestureRecognizer)
// Layout custom popups or action sheets
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.25) {
self.scrimView.alpha = 0.5
// Animate in custom popups or action sheets
}
}
func present() {
myWindow = UIWindow(frame: UIScreen.main.bounds)
myWindow?.windowLevel = UIWindowLevelAlert
myWindow?.backgroundColor = UIColor.clear
myWindow?.rootViewController = self
myWindow?.isHidden = false
}
func dismiss() {
UIView.animate(
withDuration: 0.25,
animations: {
self.scrimView.alpha = 0.0
// Animate out custom popups or action sheets
},
completion: { success in
self.myWindow = nil
}
)
}
}
To present the view:
let modalView = ModalViewController()
modalView.present()
To dismiss the view, tap anywhere on the scrim.
this code works for me, when I am presenting UIViewController with alpha != 1. present UIViewController like:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let destinationVC = storyBoard.instantiateViewController(withIdentifier: "AddComment") as! AddCommentViewController
destinationVC.modalPresentationStyle = .overCurrentContext //this line is important
destinationVC.delegate = self
destinationVC.restId = self.restaurant.id
self.present(destinationVC, animated: true, completion: nil)
then in destinationVC view controller
override func viewWillDisappear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = .clear
})
super.viewWillDisappear(true)
}
override func viewWillAppear(_: Bool) {
UIView.animate(withDuration: 1, animations: { () in
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
})
super.viewWillAppear(true)
}
and set its backgroundColor to .clear in viewDidLoad or storyboard. So UIViewController covers whole screen including status bar.
Here is the solution you might be looking for:
if let window = UIApplication.shared.keyWindow {
window.windowLevel = UIWindowLevelStatusBar + 1
}
The main idea behind this code is, window of your application has a window level which is lower than status bar window level. And what this code does is, just put your window's window level higher than status bar window level, and your window can now cover the status bar. Don't forget, this code has to be called on main thread, just before presenting your view controller. Good luck!
Custom animation transitions should be performed using UIViewControllerAnimatedTransitioning. Here is a tutorial for this purpose:
https://www.raywenderlich.com/110536/custom-uiviewcontroller-transitions
If all you want is a fade animation you can have it by changing the modalTransitionStyle property of the viewController you are going to display.
Try by fixing your code this way:
guard let shareViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ShareViewController") as! ShareViewController else {
//Fallback in case of nil?
return
}
shareViewController.modalTransitionStyle = .crossDissolve
presentViewController(shareViewController, animated: true, completion: nil)
Also please note that presentViewController(shareViewController, animated: true, completion: nil) is for swift 2. The equivalent swift 3 would be present(shareViewController, animated: true, completion: nil)
you can add this code to view controller for Swift 3:
let statusView: UIView = UIView(frame: CGRect(x: 0.0, y: -20.0, width: UIScreen.main.bounds.size.width, height: 20.0))
statusView.backgroundColor = UIColor.black
statusView.alpha = 0.8
self.addSubview(self.statusView)
You could be extremely practical and simply hide the status bar when your modal view controller is up:
override func prefersStatusBarHidden() -> Bool {
return true
}
I want to present my view controller with a custom transition so the fromViewController will be darkened as if presenting a AlertViewController.
I have created my customTransition manager:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView()
let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
let fadeRect = CGRectMake(0, 0, fromView.frame.size.width, fromView.frame.size.height)
let fadeView = UIView(frame: fadeRect)
if (self.presenting == true){
fadeView.backgroundColor = UIColor.blackColor()
fadeView.alpha = 0
fromView.addSubview(fadeView)
container.insertSubview(fadeView, aboveSubview: fromView)
} else {
// adding subviews to container
}
let duration = self.transitionDuration(transitionContext)
UIView.animateWithDuration(duration, animations: { () -> Void in
if (self.presenting == true){
fadeView.alpha = 0.5
} else {
}
}) { (Bool) -> Void in
if self.presenting{
container.addSubview(toView)
}
transitionContext.completeTransition(true)
}
}
}
I assign my transitioning manager to the toViewController as it can be seen below:
var purchaseSpecialItemsViewController = self.storyboard?.instantiateViewControllerWithIdentifier("specialItemPurchaseVC") as! BRSpecialItemPurchaseViewController
purchaseSpecialItemsViewController.transitioningDelegate = self.fadedTransitionManager
self.presentViewController(purchaseSpecialItemsViewController, animated: true, completion: nil)
The View Controller I am about to display is in storyboard with a fixed size. It has a clear background and I added a tableview there that I want to be in the center of the screen.
The TableView is displayed without the problem, but I can't see the fromViewController at all. It is just black even though I set the alpha of the black background to 0 which is later animated to 0.5.
I am presenting the view controller modally, is that causing the problem? Should I use instead a push and pop operation?
As described here: http://mathewsanders.com/custom-menu-transitions-in-swift/
I needed to add the modal presentation style like this:
purchaseSpecialItemsViewController.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
and instead getting ViewControllersViews, use
let screens: (from: UIViewController, to: UIViewController) = (transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!, transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!)
to access the ViewControllers.