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()
}
Related
I am trying to fade-in a background gradient image and slide up a uiview card from the bottom (off-screen) to the center of the uiviewcontroller - executing both animations simultaneously when the uiviewcontroller is presented modally.
What I've attempted is to set the uiviewcontroller modal transition style to cross dissolve, which would provide the fade-in effect for the background gradient image, and in viewDidAppear run the animation to slide up the uiview card from the bottom to center.
While this works, there's a slight delay with the card, and ideally I hoped both animations took place at the same time.
Is this grouping possible? Any guidance would be appreciated.
Below is relevant code in the modally presented view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
alertViewCenterYConstraint.constant += view.bounds.height
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseOut, animations: {
self.alertViewCenterYConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
}
This one works like a magic. I love it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
alertViewCenterYConstraint.constant += view.bounds.height
DispatchQueue.main.async {
UIView.animate(withDuration: 0.1, delay: 0.05, options: .curveEaseOut, animations: {
self.alertViewCenterYConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
}
}
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
I am trying to make a custom pop up over a UITableViewController that is embedded in a UINavigationController but I am experiencing two problems:
The opacity that i determined by designating an alpha value to the background colour of the UIViewcontroller for the pop up appears not to function.
The UIViewcontroller for the popup is swipe-able. If I make a left to right gesture on the screen I am able to push off the pop up. How do I prevent it from behaving like this? I am trying to show a file upload progress so it is important that the pop up is not able to be swiped away.
Please see screen shot below.
func showProgrssBarPopUp(){
let popUp = self.storyboard?.instantiateViewController(withIdentifier: "uploadPopUp") as! ProgressBarPopUpViewController
self.navigationController?.pushViewController(popUp, animated: true)
}
The lower viewcontroller content is not viewable, even though alpha value of overlaying popup viewcontroller is set to 0.5:
The entire viewcontroller for popup is swipe-able:
You can set alpha of background
self.view.backgroundColor = UIColor.white.withAlphaComponent(0.2)
You can Present ProgressBar View with modalPresentationStyle as overCurrentContext
let popUp = self.storyboard?.instantiateViewController(withIdentifier: "uploadPopUp") as! ProgressBarPopUpViewController
popUp.modalPresentationStyle = .overCurrentContext
self.present(popUp, animated: true, completion: nil)
Just override view controller's appear/dismiss method
class PopUpController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if animated {
view.backgroundColor = .clear
UIView.animate(withDuration: animationTime) {
self.view.layer.backgroundColor = UIColor.black.withAlphaComponent(0.75).cgColor
}
}
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if flag {
UIView.animate(withDuration: animationTime, animations: {
self.view.layer.backgroundColor = UIColor.clear.cgColor
}, completion: { (bool) in
super.dismiss(animated: false, completion: completion)
})
} else {
super.dismiss(animated: flag, completion: completion)
}
}
}
Use
let popUp = PopUpController()
popUp.modalPresentationStyle = .overCurrentContext
self.present(popUp, animated: true, completion: nil)
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 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)