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
Related
In my work on an iOS multi-screen App, I am trying to get my first custom animated transaction to work. Instead of using standard animations when presenting a screen modal and fullscreen, I want to have a horizontal slide in animation.
I have made this work by overriding perform in UIStoryboardSegue, and make the animation and transaction be performed from here.
Overall, it works, except from one case, which is when a navigation controller embeds a view.
In that case, I have followed a tutorial showing precisely how custom transaction is achieved by implementing the UINavigationControllerDelegate protocol for the particular navigation controller.
import Foundation
import UIKit
class AnimatedNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationController.Operation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return NavigationControllerTransition()
}
}
Unfortunately the delegate, which should return an animation object:
navigationController(_:animationControllerFor:from:to:)
is never called, and that is my primary problem.
In the tutorial mentioned, a button is used to perform a call to the navigation controller that pushes the destination view with animation set to true:
navigationController.pushViewController(view, animated:true)
In my case, I suspect that the problem is that the navigation controller pushes the view without having the animation parameter set to true, and that explains why the transaction delegate is never called.
(a confirmation off this theory will of course help a bit).
Consequently, I have tried to implement a custom segue that presents the view by calling the push function with animation set to true.
Sadly, I have not been able to get this to work.
When the navigationController.pushViewController(view, animated:true) is called, it raises an exception saying that view has already been pushed.
I have tried a lot without any luck. Code looks like this:
class NavigationControllerSegue: UIStoryboardSegue {
override func perform() {
let navigationController = self.destination as! UINavigationController
let view = navigationController.viewControllers.first!
navigationController.pushViewController(view, animated: true)
}
}
Hey first of all we are talking about two different animations... the first "UIStoryboardSegue" creates a custom subclass UIStoryboardSegue, and this is assigned to the following in question set an id to the following, below just call from the 'ViewController' a self.performSegue(withIdentifier: "YOUR-ID", sender: nil), Example of the subclass UIStoryboardSegue:
import UIKit
// Example =>
class ScaleSegue: UIStoryboardSegue {
override func perform() {
self.scale()
}
func scale() {
let toViewController = self.destination
let fromViewController = self.source
let containerView = fromViewController.view.superview
let originalCenter = fromViewController.view.center
//let originalCenter = self.posizioneClick
toViewController.view.transform = CGAffineTransform.init(scaleX: 0.05, y: 0.05)
toViewController.view.center = originalCenter
containerView?.addSubview(toViewController.view)
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseInOut, animations: {
toViewController.view.transform = CGAffineTransform.identity
}, completion: { success in
fromViewController.present(toViewController, animated: false, completion: nil)
})
}
}
Now, you have to create a segue from storyboard and set as custom class of the segue from the AttributeInspector:
Now to use this you just need to call from the view controller => self.performSegue(withIdentifier: "segue1", sender: nil). Different is the way to implement Transition with UIViewControllerTransitioningDelegate.
If you need to let me know, I can give you examples using this technique too, so long.
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
I have a MasterViewController and I am adding a ChildViewController inside a MasterViewController. Inside MasterViewController I have made top button bar , and took a view as a Container view. In this container view I want to move my ChildViewControllers. And This will happen on button click in MasterViewController
This is how I am adding the ChildViewController
func addViewControllerAsChildViewController(childVC : UIViewController) {
oldVC = newVC
childVC.view.frame = viewContainer.bounds
childVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addChildViewController(childVC)
childVC.didMove(toParentViewController: self)
// Where ViewContainer is dedicated area inside the MasterViewController
viewContainer.addSubview(childVC.view)
}
And these are my button clicks
#IBAction func onClickBtnNewList(_ sender: UIButton) {
if !(newVC is NewListVC) {
initNewListVC()
addViewControllerAsChildViewController(childVC: targetViewController)
}
UIView.animate(withDuration: 4000, animations: {
self.viewIndicators.frame = sender.frame.offsetBy(dx:0,dy:2)
})
}
#IBAction func onClickBtnSavedList(_ sender: UIButton) {
if !(newVC is SavedListVC) {
initSavedListVC()
addViewControllerAsChildViewController(childVC: targetViewController)
}
UIView.animate(withDuration:400, animations: {
self.viewIndicators.frame = sender.frame.offsetBy(dx:0,dy:2)
})
}
My ChildViewControllers gets inflated correctly. But Problem is on button Click there is a another view, which I am animating on click and moving it below the button. You can see I have used the following code in button click. The animation start and ended moving my related view to its starting position. and its not moving to my expected area see code below
UIView.animate(withDuration: 4000, animations: {
self.viewIndicators.frame = sender.frame.offsetBy(dx:0,dy:2)
})
but this is not working. But If I comment out following lines that are attaching ChildViewController , The animation do move smoothly and View moves to the target area
/*
if !(newVC is NewListVC) {
initNewListVC()
hideOrShowViewController(targetViewController: newVC!)
}
*/
Now do you have any idea why it is happening ?? what could be problem?
I am using Xcode 9 and swift 4 Please help
Issue
I have a number of cases on iOS 11 (that do not occur on iOS 10 and below) where .UIKeyboardWillChangeFrame notification is not being fired — specifically when transitioning between view controllers where both view controllers have a UITextfield which is set as the fristResponder.
Since I have UI that needs to animate above the keyboard in response to the keyboard showing, receiving this notification is essential.
On iOS 10 and below, I get the notification on both view controllers (on showing VC A and also when pushing VC B). However, on iOS 11, the notification does not fire when pushing VC B. It's as if the keyboard remained in place.
Does anyone know what the cause of this is?
Details
Both view controllers inherit from a base view controller with the following implementation:
class BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: .UIKeyboardWillChangeFrame, object: nil)
}
// Update layout when the keyboard is shown or hidden
#objc func keyboardWillChangeFrame(notification : Notification) {
// Check if got info
if (notification.userInfo == nil) {
return;
}
// Get resize properties
let dict = notification.userInfo!
let rect = self.view.convert((((dict[UIKeyboardFrameEndUserInfoKey as NSObject] as Any) as AnyObject).cgRectValue)!, from: nil)
let size = self.view.bounds.size.height - rect.origin.y
let duration = ((dict[UIKeyboardAnimationDurationUserInfoKey] as Any) as AnyObject).doubleValue
let curve = UIViewAnimationCurve.init(rawValue: (((dict[UIKeyboardAnimationCurveUserInfoKey] as Any) as AnyObject).intValue)!)
self.keyboardOffset = max(0, size)
// Set animation options
var options : UIViewAnimationOptions
switch (curve!) {
case .easeInOut:
options = UIViewAnimationOptions()
case .easeIn:
options = UIViewAnimationOptions.curveEaseIn
case .easeOut:
options = UIViewAnimationOptions.curveEaseOut
case .linear:
options = UIViewAnimationOptions.curveLinear
}
// Animate the change
UIView.animate(withDuration: duration!, delay: 0, options: options, animations: { () -> Void in
// Relayout
self.relayout()
}, completion: nil)
}
}
Example of a subclass:
class ViewControllerA: BaseViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
passwordField.becomeFirstResponder()
}
func relayout() {
// ... do animations to show stuff above keyboard.
}
}
Note that .keyboardWillShow and .keybaordWillHide are also not fired on the transition to VC B.
You need to add self.view.endEditing(force:Bool) in you first view controller's viewWillDisappear method.
Generally, you can always call endEditing method when you changing the screen.
I've worked around this by saving the keyboard height in a class variable in VC A and then passing that along to VC B.
It's not ideal, as I would prefer my VC's to be independent for those details.
I am dismissing a popover view controller programmatically. How can i detect that in my first view controller? Is there a way to send values from the popover to the first one?
Note: popoverPresentationControllerDidDismissPopover does not work when dismissed programmatically.
Any proposition?
this is my code in the main view controller:
let addFriendsPopoverViewController = storyboard?.instantiateViewControllerWithIdentifier("HomeEmotionPopOver") as! EmotionPopOverViewController
addFriendsPopoverViewController.modalInPopover = true
addFriendsPopoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
addFriendsPopoverViewController.preferredContentSize = CGSizeMake(100, 100)
let popoverMenuViewController = addFriendsPopoverViewController.popoverPresentationController
popoverMenuViewController!.permittedArrowDirections = .Any
popoverMenuViewController!.delegate = self
popoverMenuViewController!.sourceView = self.view
let height = (self.tableView.rowHeight - HeartAttributes.heartSize / 2.0 - 10) + (self.tableView.rowHeight * CGFloat((sender.view?.tag)!)) - 50
popoverMenuViewController!.sourceRect = CGRect(
x: 30,
y: height,
width: 1,
height: 1)
presentViewController(
addFriendsPopoverViewController,
animated: true,
completion: nil)
and in the popover view controller, i'm dismissing it from a button IBAction:
#IBAction func dismissPop(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
The way you have worded your question is that you are looking for a function on the main view controller that is called when a popover is dismissed.
This technically happens with viewDidAppear(animated:). However, it isn't a full proof solution. If your popover doesn't cover the full screen context, this function wont fire, so it is an unreliable solution.
Really what you want is to invoke a function from the popover alerting the main view controller that it has finished/dismissed. This is easily done with a delegate protocol
protocol PopoverDelegate {
func popoverDismissed()
}
class PopoverViewController {
weak var delegate: PopoverDelegate?
//Your Popover View Controller Code
}
Add the protocol conformance to your main view controller
class MainViewController: UIViewController, PopoverDelegate {
//Main View Controller code
}
Then you need to set the delegate to for the popover to be the main view controller.
let addFriendsPopoverViewController = storyboard?.instantiateViewControllerWithIdentifier("HomeEmotionPopOver") as! EmotionPopOverViewController
addFriendsPopoverViewController.delegate = self
//The rest of your code
Finally, call this delegate function from your popover view controller when you dismiss.
#IBAction func dismissPop(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
delegate?.popoverDismissed()
}
And in your main view controller, implement the delegate method
func popoverDismissed() {
//Any code to run when popover is dismissed
}
The trick is to dismiss the segue yourself but make it seem that the user initiated it so it can be detected by the delegate method popoverPresentationControllerDidDismissPopover().
I did it by adding a completion closure to the presentingViewController dismiss() function and directly invoked the routine.
if let pvc = self.presentingViewController {
var didDismiss : ((UIPopoverPresentationController) -> Void)? = nil
if let delegate = popoverPresentationController?.delegate {
// check it is okay to dismiss the popover
let okayToDismiss = delegate.popoverPresentationControllerShouldDismissPopover?(popoverPresentationController!) ?? true
if okayToDismiss {
// create completion closure
didDismiss = delegate.popoverPresentationControllerDidDismissPopover
}
}
// use local var to avoid memory leaks
let ppc = popoverPresentationController
// dismiss popover with completion closure
pvc.dismiss(animated: true) {
didDismiss?(ppc!)
}
}
It is working fine for me.