I have one main ViewController, which will render different views from other view controllers (mostly table views), by using addChild:Vc i can present and remove the child view, but the problem is it's a view hierarchy, so view layers will come over each other and every child view has a button which will dismiss itself and re-presents the previous view in view hierarchy. exactly like Navigation Bar back button.
So far what i have done is an UIViewController Extension which is:
func addChildVC(_ child: UIViewController,
centerWith center: CGPoint? = CGPoint(x: 0.0, y: 0.0),
insertInView insertIn: UIView? = nil,
transition: UIView.AnimationOptions? = [],
completion: ((Bool) -> Void)? = nil)
{
self.addChild(child)
if let center = center
{
child.view.center = center
}
if let insertIn = insertIn
{
insertIn.insertSubview(child.view, aboveSubview: insertIn.self)
} else {
self.view.addSubview(child.view)
}
child.didMove(toParent: self)
}
func removeChildVC()
{
willMove(toParent: nil)
view.removeFromSuperview()
removeFromParent()
}
You need a navigation for the contained vc
Step 1
Step 2 select the child vc and
Step 3
Now you can push and pop inside that child vc
Related
I've a segmented control, clicking on segments shows views by adding view to a container view. When other segment control is clicked older view is removed and new view is added by using function given below. Adding VC in this way calls viewDidLoad where I fetch data from API. This calls API every time segment is clicked as VC is added. How do I prevent calling API every time view is added?
Is there any way we can hide old view instead of removing it, this will prevent instantiating VC every time a segment is clicked.
Alternatively is there any other better way to shows VCs on segment click?
#IBAction func show(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
let vc1 = VC1.instantiate()
self.cycleFromViewController(oldViewController: self.currentVC!, toViewController: vc1)
self.currentVC = vc1
case 1:
let vc2 = VC2.instantiate()
self.cycleFromViewController(oldViewController: self.currentVC!, toViewController: vc2)
self.currentVC = vc2
default:
break
}
}
func cycleFromViewController(oldViewController: UIViewController, toViewController newViewController: UIViewController) {
oldViewController.willMove(toParent: nil)
self.addChild(newViewController)
self.addSubview(subView: newViewController.view, toView:self.containerView!)
newViewController.view.alpha = 0
newViewController.view.layoutIfNeeded()
UIView.animate(withDuration: 0.5, animations: {
newViewController.view.alpha = 1
oldViewController.view.alpha = 0
},
completion: { finished in
oldViewController.view.removeFromSuperview()
oldViewController.removeFromParent()
newViewController.didMove(toParent: self)
})
}
Create
var vc1,vc2,currentVC:UIViewController?
Then inside viewDidLoad create and assign vc1 and vc2 and set currentVC = vc1
after that play with their view.isHidden
Using Xcode 10+, Swift 4, iOS 11.4+
First let me say that I'm not using a Navigation Controller -
I'm adding a ViewController to another as a child using this basic code:
topController.addChildViewController(childVC)
topController.view.addSubview(childVC.view)
childVC.didMove(toParentViewController: topController)
The child is smaller than the parent and has a few buttons, one of which will animate it out of view.
I'm not using present/dismiss as it always covers the entire screen.
I'd like it to be modal - once it's animated into place, nothing else on screen (behind it) should be usable until it is animated out of view.
How can I make the childVC be modal?
You could try adding the controller to a UIWindow which has windowLevel = UIWindowLevelAlert + 1 instead. Then after the dismiss animation finishes you could remove the window. Here is a sample code snippet that seems to work:
func presentChildVC() {
modalWindow = UIWindow(frame: UIScreen.main.bounds)
let rootController = UIViewController()
rootController.view.backgroundColor = .clear
rootController.addChild(childController)
rootController.view.addSubview(childController.view)
childController.didMove(toParent: rootController)
modalWindow?.rootViewController = rootController
modalWindow?.windowLevel = .alert + 1
modalWindow?.makeKeyAndVisible()
modalWindow?.backgroundColor = .clear
UIView.animate(withDuration: 2, animations: {
self.childController.view.alpha = 1
})
}
func dismissChildVC() {
UIView.animate(withDuration: 2, animations: {
self.childController.view.alpha = 0
}, completion: { _ in
self.modalWindow?.isHidden = true
self.modalWindow = nil
})
}
1) The child is smaller than the parent:-
You just need to update your child view frame same like parent view.
topController.addChildViewController(childVC)
topController.view.addSubview(childVC.view)
**childVC.view.frame.size.height = self.view.frame.size.height**
childVC.didMove(toParentViewController: topController)
2) has a few buttons, one of which will animate it out of view :-
Set Click Event on buttons like this to remove child view from parent
self.willMove(toParentViewController: nil)
self.view.removeFromSuperview()
self.removeFromParentViewController()
I have a storyboard segue, and while showing the new view, I want an UIView to always stay on top, so the segue does not affect it. Tried animating insertSubview, but it does not have the push from bottom animation.
Here is some code I quickly whipped up showing two view controllers, the one you are going to and one from. I put the animation in a function called buttonPressed, but it should go whatever function that calls the transition. Rest of the code is pretty self explanatory.
For this to work both view controllers will need a view with same name (or different names but keep track of which is which), I use IBOutlet staticView. And then in interface builder make sure they have the same constraints or frame so that when you set on vc's static view to another it sticks to same spot.
class ViewControllerFrom: UIViewController {
#IBOutlet weak var staticView: UIView!
#IBAction func buttonPressed(_ sender: Any) {
let toVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "toVC") as! ViewControllerTo
//Add nextVC's view to ours as subview
self.view.addSubview(toVC.view)
//Set its starting height to be below current view.
toVC.view.frame = CGRect(x: 0, y: view.frame.height, width: view.frame.width, height: view.frame.height)
//This is important to make sure staticView stays in front of view animating in
view.bringSubview(toFront: staticView)
if toVC.staticView != nil {
toVC.staticView.isHidden = true
}
//I use animation duration 0.4, close to default animations by iOS.
UIView.animate(withDuration: 0.4, animations: {
toVC.view.frame.origin.y = 0
}, completion: { (success) in
if success {
//Now that view is in place, set static view from old vc to new vc and reshow it. Then do the actual presentation unanimated.
toVC.staticView.isHidden = false
toVC.staticView = self.staticView
self.present(toVC, animated: false, completion: nil)
}
})
}
}
class ViewControllerTo : UIViewController {
#IBOutlet weak var staticView: UIView!
}
Apple discusses how to have a container view controller transition between two child view controllers in this document. I would like to animate a simple push vertical slide up identical to UIModalTransitionStyleCoverVertical in UIModalTransitionStyle. However, transitionFromViewController only allows use of UIViewAnimationOptions, not transition styles. So how would one animate sliding a view up?
It's odd that to transition between child view controllers you can't call a simple push method similar in UINavigationController to animate the transition.
Load child view, set frame with origin.y under bottom screen. After change it to 0 in animation block. Example:
enum Animation {
case LeftToRight
case RightToLeft
}
func animationForLoad(fromvc: UIViewController, tovc: UIViewController, with animation: Animation) {
self.addChildViewController(tovc)
self.container.addSubview(tovc.view)
self.currentVC = tovc
var endOriginx: CGFloat = 0
if animation == Animation.LeftToRight {
tovc.view.frame.origin.x = -self.view.bounds.width
endOriginx += fromvc.view.frame.width
} else {
tovc.view.frame.origin.x = self.view.bounds.width
endOriginx -= fromvc.view.frame.width
}
self.transition(from: fromvc, to: tovc, duration: 0.35, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
tovc.view.frame = fromvc.view.frame
fromvc.view.frame.origin.x = endOriginx
}, completion: { (finish) in
tovc.didMove(toParentViewController: self)
fromvc.view.removeFromSuperview()
fromvc.removeFromParentViewController()
})
}
Above code is transition between 2 child view with push and pop horizontal animation.
I have a tab bar controller and 3 childs of it, also I have another view which I made a custom segue from the childs to the view controller, and also a custom unwind segue from the view controller to the child. The problem is that while the unwind is happening, the tab bar is hidden and it shows when the unwind is finished.
HereĀ“s a GIF example:
Here's my code of the custom segue unwind:
import UIKit
class AddMeCustomSegueUnwind: UIStoryboardSegue {
override func perform() {
// Assign the source and destination views to local variables.
let secondVCView = self.sourceViewController.view as UIView!
let firstVCView = self.destinationViewController.view as UIView!
let screenHeight = UIScreen.mainScreen().bounds.size.height
let window = UIApplication.sharedApplication().keyWindow
//window?.insertSubview(firstVCView, aboveSubview: secondVCView)
//window?.insertSubview(firstVCView, belowSubview: secondVCView)
window?.insertSubview(firstVCView, atIndex: 0)
// Animate the transition.
UIView.animateWithDuration(0.4, animations: { () -> Void in
secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, -screenHeight + 64)
}) { (Finished) -> Void in
self.sourceViewController.dismissViewControllerAnimated(false, completion: nil)
}
}
}
To make the unwind work as I said, the unwind segue needed to be done from the tab bar controller so I created a UITabBarController and add the same unwind to it.