I'm trying to present a view controller with custom animation, so I came up with this code. When I tried to present the view controller, I got no error but the app crashes. Can someone tell me why my app crashes?
My code looks like this
func presentLeftToRight<T>(viewName: String, class: T.Type) where T: DefaultView {
let presentView = self.storyboard?.instantiateViewController(withIdentifier: viewName) as! T
presentView.loadViewIfNeeded()
presentView.topContainer.alpha = 0
presentView.bgMenuView.alpha = 0
presentView.mainButton.alpha = 0
self.view.insertSubview(presentView.view, belowSubview: scrollView)
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5, animations: {
self.scrollView.frame.origin.x = self.view.frame.width
}, completion: { _ in
self.present(presentView, animated: false, completion: nil)
})
}
Related
When you present (popover) a view controller the previous view controller kind of backs out of the screen. Is there any way to stop that from happenening.
Here is an example of what is happening: https://imgur.com/a/DSKC6vF
And here is an example of what I want to happen: https://imgur.com/a/vyjA1Jv
The only code I'm using for presenting it
let viewController = UIStoryboard(name: "StoryboardName", bundle: nil).instantiateViewController(identifier: "IdentifierName")
self.present(viewController, animated: true, completion: nil)
If you want something like this:
First ViewController:
let sb = UIStoryboard.init(name: "Appointment", bundle: nil)
let popVC = sb.instantiateViewController(withIdentifier: "AppointConfirmationPopUpVC") as! AppointConfirmationPopUpVC
self.addChild(popVC)
popVC.view.frame = self.view.frame
self.view.addSubview(popVC.view)
popVC.didMove(toParent: self)
Second ViewController:
#IBOutlet weak var bottomView: UIView!
viewdidLoad()
{
self.view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
showAnimate()
}
func showAnimate(){
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseIn],
animations: {
self.bottomView.center.y -= self.bottomView.bounds.height
self.bottomView.layoutIfNeeded()
}, completion: nil)
}
func hideAnimate()
{
self.tabBarController?.tabBar.isHidden = false
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear],
animations: {
self.bottomView.center.y += self.bottomView.bounds.height
self.bottomView.layoutIfNeeded()
self.view.center.y += self.view.bounds.height
self.view.layoutIfNeeded()
}, completion: {(_ completed: Bool) -> Void in
})
}
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)
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.
I have a view controller (VCA) that modally presents another view controller VCB:
presentViewController(VCB, animated: true, completion: nil)
If VCB has a modalPresentationStyle = .OverFullScreen (same applies for .OverCurrentContext), why when it is dismissed:
dismissViewControllerAnimated(true, completion: nil)
does the following code when the transition is finished result in VCA turning black?
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewContrllerKey)!
let fromVc = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
var containerView = transitionContext.containerView()!
if presenting {
toVC.view.alpha = 0
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
} else {
containerView.insertSubview(toVC.view, belowSubview: fromVC.view)
}
UIView.animateWithDuration(transitionDuration(transitionContext), delay: 0.0, options: [.CurveEaseInOut], animations: {
if self.presenting {
self.toVC.view.alpha = 1
} else {
self.fromVC.view.alpha = 0
}
} , completion: { finished in
self.transitionContext.completeTransition(true)
})
}
Is there a way to fix this without removing the line containerView.insertSubview(toVC.view, belowSubview: fromVC.view) or checking the value of fromVC.modalPresentationStyle before executing the above line? i.e. is there some parameter I can set in VCA's or VCB's initialiser?
Many thanks for any help.