Segue animation isn't fully working - ios

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)

Related

UIPageViewController between two UIViewControllers

I've just started programming in Swift, what I'm trying to accomplish is a very simple app with an initial UIViewController, a UIPageViewController that shows some book pages and a destination UIViewController.
My approach so far is this:
The UIViewController1 is loaded and has a showPage button that simply shows UIPageViewController
present(walkthroughViewController, animated: true, completion: nil)
When the user reaches the last page of the UIPageViewController, I show the destination UIViewController2, addressing the segue from the start UIViewController
override func onUIPageViewControllerRigthClosing(){
let pvc = self.presentingViewController as! StartPageController
dismiss(animated: true){
pvc.performSegue(withIdentifier: "startTest", sender: nil)
}
}
Everything works correctly, but the problem is that when UIPageViewController is dismissed, the Starting UIViewController is showed and then is showed the second with the animated segue.
What I am trying to achieve is to directly display the target UiViewController to the user on the dismiss of the UIPageViewController, without showing the transition with animation from start View to the destination View.
I'm completely wrong approaching or there is a way to do the segue before dismissing the UIPageViewController?
Here I created a gif that shows the problem, when I close the UIPageViewController I see the previous view in transition: GIF demo
I suggest you using this approach: for these screens transitions use childViewControllers instead of presenting them modally and dismissing with default UIKit functions.
You have problems with naming, so let me rename view controllers.
Say, you have:
RootViewController (the first screen, user see after
app launch).
OnboardingViewController (your pageViewController or other container)
AppContentViewController (actually app main screen)
I suggest you using this approach: for screens transitions on RootViewController use childViewControllers instead of presenting them modally and dismissing with default UIKit functions.
Here is sample code that works with childViewControllers
extension UIViewController {
func displayChildController(_ content: UIViewController, duration: TimeInterval = 0.4, animation: (() -> ())? = nil, completion: #escaping () -> () = {}) {
content.view.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
view.addSubview(content.view)
addChildViewController(content)
UIView.animate(withDuration: animation != nil ? duration : 0, animations: {() -> Void in
animation?()
}, completion: {(_ finished: Bool) -> Void in
content.didMove(toParentViewController: self)
completion()
})
}
func hideChildController(_ content: UIViewController, duration: TimeInterval = 0.4, animation: (() -> ())? = nil, completion: #escaping () -> () = {}) {
UIView.animate(withDuration: animation != nil ? duration : 0, animations: {() -> Void in
animation?()
}, completion: {(_ finished: Bool) -> Void in
content.willMove(toParentViewController: nil)
content.view.removeFromSuperview()
content.removeFromParentViewController()
completion()
})
}
}
Here is "algorithm":
I assuming that you are using single storyboard with all these view controllers.
On OnBoardingViewController declare onDoneCallback:
class OnBoardingViewController: ... {
var onDoneCallback = {}
...
}
On RootViewController when you need present OnboardingViewController:
func presentOnboardingScreen() {
let onboardingVC = self.storyboard?.instantiateViewController(withIdentifier: "OnboardingViewController") as! OnboardingViewController
onboardingVC.transform = .init(translationX: 0, y: self.view.frame.height)
onboardingVC.onDoneCallback = {
self.presentAppContentAfterOnboarding() // see below
}
displayChildController(onboardingVC, duration: 0.3, animation: {
vc.view.transform = .identity
})
}
When you need call onDoneCallback closure on OnboardingViewController
presentAppContentAfterOnboarding method on RootViewController could look like:
func presentAppContentAfterOnboarding() {
let onboardingVC = self.childViewControllers.last as! OnboardingViewController
let appContentVC = self.storyboard?.instantiateViewController(withIdentifier: "AppContentViewController") as! AppContentViewController
displayChildController(appContentVC)
view.insertSubview(appContentVC.view, belowSubview: onboardingVC.view)
hideChildController(childVC, duration: duration, animation: {
onboardingVC.view.transform = .init(translationX: 0, y: self.view.frame.height)
})
}
Note. Don't forget to set Storyboard ID of OnboardingViewController and AppContentViewController in your storyboard.
Here is the sample project

UIView won't animate after Tabbar selects UIViewController

I have a UIViewController VC1 that contains a UIImageView loadingImg. When you segue from a previous UIViewController VC0, it animates. Here is some code from VC1:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
animate()
}
func animate() {
UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .curveLinear], animations: {
self.loadingImg.transform = CGAffineTransform(rotationAngle: .pi / 2)
}, completion: nil)
}
I also have a tabbar, where VC0 is one of the root view controllers. Once VC0 has segued to VC1, when you click on to another root view controller and then back, loadingImg is no longer animating. How can I keep loadingImg animating even if I'm using tabbar to switch to different view controllers and back?
You need to set original position of UIView when you switch UIViewController in ViewDidAppear like below
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.loadingImg.transform = .identity
self.view.layoutIfNeeded()
}

Custom Bottom Bar transition to another view in Swift?

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)
}

Pop Up View in Swift

I have a popover view (without a tab bar) that is popped over to a view controller with a tab bar. In the view controller with a tab bar I set up a button to click, so that the view controller pops up:
#IBAction func PopUpClicked(_ sender: UIButton) -> Void {
let popOverVC = UIStoryboard(name: "SpinningWheel", bundle: nil).instantiateViewController(withIdentifier: "PhotoPopUp") as! PopUpViewController
self.addChildViewController(popOverVC)
popOverVC.view.frame = self.view.frame
self.view.addSubview(popOverVC.view)
popOverVC.didMove(toParentViewController: self)
}
And in the popOver view controller, i animated the pop over.
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.8)
self.showAnimate()
// Do any additional setup after loading the view.
}
func showAnimate()
{
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0;
UIView.animate(withDuration: 0.25, animations: {
self.view.alpha = 1.0
self.view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
});
}
func removeAnimate()
{
UIView.animate(withDuration: 0.25, animations: {
self.view.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.view.alpha = 0.0;
}, completion:{(finished : Bool) in
if (finished)
{
self.view.removeFromSuperview()
}
});
}
But when the popover happens, Everything has a faded black background, which I want except for the tab bar. I would like for the pop over View to also pop over the tabbar and for the faded black background to go over it.
This is what I want it to look like:
With the black faded background covering the tabbar
This happens, because you have to present your popover as a modal ViewController. To achieve this you have to set the modal presentation style before presenting your popover from your target ViewController. This code should be called in your presenting ViewController:
let vc = YourPopOverViewController(nib: UINib(name: "PopOverViewController", bundle: nil), bundle: nil)
vc.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
tabBarController.present(vc, animated: true)
EDIT:
This should do the trick if you have designed your PopOverViewController as a fullscreen ViewController. I have done this a bunch of times and have left the space which should not be presented as clear background:
#IBAction func PopUpClicked(_ sender: UIButton) -> Void {
let popOverVC = UIStoryboard(name: "SpinningWheel", bundle: nil).instantiateViewController(withIdentifier: "PhotoPopUp") as! PopUpViewController
popOverVc.modalPresentationStyle = UIModalPresentationStyle.OverCurrentContext
tabBarController.present(popOverVC, animated: true)
}
This 3rd party EzPopup can resolve the problem easily: https://github.com/huynguyencong/EzPopup
// init YourViewController
let contentVC = ...
// Init popup view controller with content is your content view controller
let popupVC = PopupViewController(contentController: contentVC, popupWidth: 100, popupHeight: 200)
// show it by call present(_ , animated:) method from a current UIViewController
present(popupVC, animated: true)

Swift Switching To and FromTable ViewController Issue Using Custom Segue

I have run into an issue when using a custom segue. I have two tableviews that I'm an trying to switch back and forth from. When I click on a cell in tableview1 it should take me to tableview2. I have a button on tableview2 that connects to the exit of the storyboard. From there it should take me back to tableview1 but whenever I press the button, the application crashes with a BAD_ACCESS error.
Here is my custom segue class:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil) //The application crashes and highlights this line as the error.
})
}
}
I have implemented this method in my tableViewController1:
#IBAction func prepareForUnwind(segue: UIStoryboardSegue) {
}
Not sure why the tableview2 does not dismiss.
EDIT: The issue had to do with needing a navigation controller.
The problem is that you are presenting the toViewcontroller each time a segue is performed. So the app presents table2 over table1, and then tries again to present table1 over table2 on the unwind.
Modify your custom segue to check - essentially - which direction you're going:
class TableViewSegue: UIStoryboardSegue {
override func perform() {
scale()
}
func scale () {
let toViewcontroller = self.destination
let fromViewcontroller = self.source
let containerView = fromViewcontroller.view.superview
let originalCenter = fromViewcontroller.view.center
toViewcontroller.view.transform = CGAffineTransform(scaleX: 0.05, y: 0.05)
toViewcontroller.view.center = originalCenter
containerView?.addSubview(toViewcontroller.view)
let fromP = fromViewcontroller.presentingViewController
UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseInOut, animations: {
toViewcontroller.view.transform = CGAffineTransform.identity
}, completion: { success in
// if nil, we are presenting a new VC
if fromP == nil {
fromViewcontroller.present(toViewcontroller, animated: false, completion: nil)
} else {
fromViewcontroller.dismiss(animated: false, completion: nil)
}
})
}
}
Note: This is assuming:
you are not trying to push/pop within a UINavigationController ... you'd need to add some other checks to handle that.
you are only going one-level-in, that is, you are not presenting, presenting, presenting, etc. and then trying to unwind.

Resources