My app is using Tab Bar Controller which contains several View Controller in different tab. When user open the app, they will firstly enter FirstView. I would like to put some method in SecondView which refresh the FirstView. This is my FirstViewController.swift:
class FirstViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
I have tried to put
FirstViewController().viewDidLoad()
in my SecondViewController.swift, but this is not working. Is there any better way to refresh the FirstView?
Try this way by make a static reference of firstViewController and then through this reference you can call any function
class ViewController: UIViewController {
static var firstVC : ViewController?
override func viewDidLoad() {
super.viewDidLoad()
print("m on FirstViewController ")
ViewController.firstVC = self
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("m on SecondViewController ")
ViewController.firstVC?.viewDidLoad()
}
}
you can try this:-
instead of 0 pass the index of your FirstViewController
if let firstVC = self.tabBarController?.viewControllers?[0] as? FirstViewController {
firstVC.viewDidLoad()
}
You should not call viewDidLoad yourself. Its called only once when the view is loaded.
If you want to update the controllers view just before its displayed, you can use viewWillAppear for changing the layout or whatever you want to do.
The issue you're battling it how to tell another view controller that it needs to update its view. For this, you have two potential solutions because effectively, you're determining the best way to communicate between different objects.
Notifications are loosely decoupled and tend to be useful for one to many relationships. One object can fire off a notification and one or more objects can be listening for that notification. In your situation, a notification can be broadcast when a certain piece of state has changed in one view controller, and the other view controller can observe that notification so it can be notified when it should change.
Delegates are more closely coupled because they're one to one. They are often times implemented by creating a delegate property on an object that conforms to some protocol. Another object then assigns that delegate property to itself and implements the protocol so its implementation will be invoked whenever that function is called on the delegate. In your situation, each view controller could have a delegate property for some protocol(s). The tab bar controller can assign the delegate property to itself and handle the implementations of these functions. Therefore, whenever a change happens and a delegate is invoked, the tab bar controller can take can responsibility of telling which view controllers to update their view.
There are also of course other ways of handling your situation such as updating the view in viewWillAppear. This way, whenever a view controller appears on the screen, some code can execute that will update its view.
It ultimately depends on how you're storing application state and the design of your application.
Related
I have a view controller that manages my entire view. Inside this view, I would like to create another four views which each have some button selector functions. I was wondering if it would be possible/smart for me to create a new View Controller class that has the functionality of these four sub-views and then instantiate these view controllers in the view controller that manages the full view?
Yes it is certainly possible. And it is often smart to do so. In your case it sounds like you may want to subclass UIView or UIControl though.
One of the main reasons to create a view controller is to keep track of the life cycle events of a view. In the view controller methods such as viewDidLoad() or viewDidAppear()
Creating view controllers works well for cases such as pages and segments, and navigation with UITabViewControllers and UINavigationControllers
You can generally just create view controller's and use them, something like this:
class MyViewController: UIViewController {
override func viewDidLoad() {
let subViewController = AnotherViewController()
view.addSubview(subViewController.view)
}
}
class AnotherViewController: UIViewController {
}
I am trying to addObserver to all my views, when I start my application.
When there is a post coming I want to display a Modal View on top of the current ViewController.
Is there a way to install it directly on every View or do I need to do the
viewWillAppear : add
viewDidDisappear : remove
workaround each time ?
You could have created one superclass for all you view controllers and override viewWillAppear/viewDidDisappear there.
If there is no exception and you want to present a modal view controller no matter what view controller is currently on screen, you can present it over self.window.rootViewController in AppDelegate's didReceiveRemoteNotification method.
Create parent class like this and subclass all other classes
import UIKit
class TemplateClassVC: UIViewController {
override func viewWillAppear() {
}
override func viewDidDisappear() {
}
}
and find top viewcontroller like this
Get top most UIViewController
What I have so far
I have a view controller ViewController1 which has 2 segues, one that goes to ViewController2 and one that comes back.
The segue from vc2 - vc1 is called "ViewC2toViewC1Segue"
On ViewController2 I have a UIScrollView that loads in two new viewcontrollers and allows me to scroll left or right to view them.
All these work fine, the data shows ok and everything displays nicely. However, On one of these subviews I want to be able to display an option to go back to ViewController1.
in my naivety I tried just using:
performSegueWithIdentifier("ViewC2toViewC1Segue", sender: self)
I hope this image helps explain:
The Problem
These two viewcontrollers that are loaded in the UIScrollView or not on the main storyboard so I can not CTRL and DRAG.
Question
How do I access the segue (ViewC2toViewC1Segue) of the view controller (ViewController2) that is holding the UIScrollView from one of UIScrollViews Child view containers.
I hope that makes sense.
The best way to do this is with a delegate protocol. Your parent view controller would be the delegate of the child. When the button is pushed on the child, it messages its delegate (the parent, who has the scroll view), and the parent handles scrolling to the other view controller.
In your ChildViewController file, you want to do 3 things:
Define the delegate protocol. This is a set of functions that the delegate object needs to implement.
Add a delegate property to the ChildVC class. This allows the ChildVC to call functions on the delegate.
Call the delegate function when the button is pressed
The protocol declaration would look something like this
protocol ChildViewControllerDelegate: class {
func childViewControllerDidSelectBack(childViewController: ChildViewController)
}
The delegate variable declaration would look like:
class ChildViewController: UIViewController {
weak var delegate: ChildViewControllerDelegate?
}
To call the delegate function, in your button handler code simply write:
delegate.childViewControllerDidSelectBack(self)
In your ParentViewController file, you want to do 3 things:
Set yourself as the delegate for the ChildVC
Declare that you conform to the ChildVCDelegate protocol
Implement the delegate protocol methods
To set yourself as the delegate, whenever you instantiate the child VC, do something like:
childVC.delegate = self
To declare that you conform to the protocol, make your class definition look like:
class ParentViewController: UIViewController, ChildViewControllerDelegate
Lastly, you need to implement the protocol function
func childViewControllerDidSelectBack(childViewController: ChildViewController){
// code to scroll the scrollview
}
}
Hopefully this helps!
You can use notification approach here.
On button click of one of your ViewController (in scrollview) post
notification.
Add observer in ViewController2's viewDidLoad method.
Go back to ViewController1 in selector method.
===================================================
//ViewController2
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(navigateToViewController1) name:#"navigateToViewController1" object:nil];
// Do any additional setup after loading the view from its nib.
}
-(void)navigateToViewController1 {
//[self.navigationController popViewControllerAnimated:YES];
OR
// perform segue
}
//In button click event of your viewcontroller inside scrollview.
[[NSNotificationCenter defaultCenter] postNotificationName:#"navigateToViewController1" object:nil];
I am currently setting up a Tab Bar Application for iOS.
Normally, I would use an overridden method like prepareforSeque for dependency injection when changing viewControllers, but that method is not called when the UITabBarController changes its active child ViewController. How do I correctly do dependency injection into UITabBarController child ViewControllers?
In the RootViewController's viewDidLoad you can iterate thru childViewControllers and find the various child controllers that you want and set the dependency to each of them. In this case the dependency will be available in viewDidLoad of the child view controllers. Tab bar instantiates the child view controller instances but does not load the view until its required.
Once the tab bar view controller is loaded you can use the delegate methods to inject updated dependencies and use it in viewDidAppear because viewDidLoad will not get called once its selected in the tab bar.
With some extra research, I have come up with an answer. Thanks to Will-m for the clue I needed. The current cavaet with this answer is that the first view loaded by the TabBarController will not get injected.
In order to inject data into ViewControllers from a UITabBarController, you need to do the following:
First, you need to set the RootViewController to be its own delegate when it loads.
You don't necessarily need the controller class to be its own delegate
unless you need to inject the required data from another class directly to the UITabBarController.
You also need to declare the delegate class as compliant to UITabBarControllerDelegate protocol.
// Declare UITabBarControllerDelegate protocol
class RootViewController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Set class delegate to self
self.delegate = self
}
}
You need to set the RootViewController's delegate because the
delegate's protocol contains an important method:
tabBarController(_:shouldSelectViewController:). This method is
called when the RootViewController changes its active tab.
The "viewController" parameter of tabBarController(_:shouldSelectViewController:) is the instance of the child ViewController which the TabBarController is switching to. Provided you have assigned a protocol to that ViewController (so that the compiler knows your variable is declared in the class), you can inject the variable into the child.
So add the function to the RootViewController class like this:
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) {
// Get your view controller using the correct protocol.
// Use guard to make sure the correct type of viewController has been provided.
guard let vc = viewController as? YourProtocol
else { fatalError("wrong view controller type") }
// Assign the protocol variable to whatever you want injected into the class instance.
vc.VariableInYourProtocol = InjectedVariable
}
That's it. If you need to support controllers of different protocols, I might write something up on using a switch statement to do that. That
s just not something I've needed to work with as of yet.
Also, as a note, this method is applicable for CoreData practices in which only one managedObjectContext instance is passed between active ViewControllers. This method is used rather than retrieving different instances of the context directly from the application delegate for each ViewController.
I'm presenting a modal view controller using a custom transition (by setting its modelPresentationStyle to UIModalPresentationCustom, providing a transitioning delegate, and UIViewControllerAnimatedTransitioning object).
In the presented view controller, I have an unwind segue hooked up to a button. The segue fires just fine; the IBAction method in the presenting view controller is called, and so is prepareForSegue in the presented view controller. However, the presented view controller is not dismissed, and the appropriate transitioning delegate method (animationControllerForDismissedController:) is not called.
If, however, I set the presented view controller's modalPresentationStyle to UIModalPresentationFullScreen (the default), the view controller is dismissed properly (this breaks my custom transition, though).
I'm at a complete loss at what to do here. I've looked through Apple's documentation, and didn't notice anything saying that one had to do special things with unwind segues when dealing with custom transitions.
I'm aware that I could call dismissViewControllerAnimated:completion: in the IBAction method of the presenting view controller, but I'd rather use that as a last resort, and get the unwind segue working the way it should (or at least know why it's not working :) ).
Any help would be much appreciated,
Thanks in advance
It seems that if you use UIModalPresentationCustom to present the controller with a custom transition manager, you also need to use a custom transition manager to dismiss it (which makes sense I guess, since you can do all kinds of weird stuff in the animator object and UIKit can't be sure that just dismissing the screen as usual will completely restore the original state - I just wish it told you that explicitly...).
Here's what I've done to fix this in my app:
override segueForUnwindingToViewController in the parent view controller (the one to which you're moving after the dismiss animation) and return an instance of your UIStoryboardSegue, either the one you've used for the original transition or a new separate class
if the unwind segue's target view controller is in a navigation hierarchy, then you need to override that method in the navigation controller instead
in the perform method call dismissViewControllerAnimated
the presented view controller needs to still hold a valid reference to the transitioning delegate, or you'll get an EXC_BAD_ACCESS (see DismissViewControllerAnimated EXC_Bad_ACCESS on true) - so either make it keep the delegate as a strong reference as described in that thread, or assign a new one before calling dismissViewControllerAnimated (it's possible that changing modelPresentationStyle to e.g. full screen before dismissing would work too, but I haven't tried that)
if the dismiss animation needs to do any non-standard things (mine luckily didn't), override animationControllerForDismissedController in the transition manager object and return a proper animator
if the target view controller is in a navigation hierarchy, then you also need to manually pop the navigation stack to the target controller before dismissing the presented screen (i.e. target.navigationController!.popToViewController(target, animated: false))
Complete code sample:
// custom navigation controller
override func segueForUnwindingToViewController(toViewController: UIViewController,
fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return CustomSegue(
identifier: identifier,
source: fromViewController,
destination: toViewController
)
}
// presented VC
var customTransitionManager: UIViewControllerTransitioningDelegate?
// custom segue
override func perform() {
let source = sourceViewController as! UIViewController
if let target = destinationViewController as? PresentedViewController {
let transitionManager = TransitionManager()
target.modalPresentationStyle = .Custom
target.customTransitionManager = transitionManager
target.transitioningDelegate = transitionManager
source.presentViewController(target, animated: true, completion: nil)
} else if let target = destinationViewController as? WelcomeViewController {
target.navigationController!.popToViewController(target, animated: false)
target.dismissViewControllerAnimated(true, completion: nil)
} else {
NSLog("Error: segue executed with unexpected view controllers")
}
}
I also met this problem when I need to pass data back from the modalpresented view.
I wandering around Google and here for a couple of hours but I couldn't find an answer that is easy to understand for me. But I did get some hint and here's a work around.
It seems that because it has to pass data back, and the dismissing process from the automatic Unwind is prior before the data passing which prevented the ViewController being dismissed. So I think that I have to manually dismiss it once one more time.
I got some luck here. I didn't notice that it was a child viewcontroller. I just configured it from the storyboard.
And then in the Unwind function, I added to lines to remove the child viewcontroller and the child view. I have no code in the sourceViewController.
Swift 4.1
#IBAction func unwindToVC(sender :UIStoryboardSegue){
if let source = sender.source as? CoreLocationVC{
if source.pinnedCity != nil{
clCity = source.pinnedCity
}
if source.pinnedCountry != nil {
clCountry = source.pinnedCountry
}
if source.pinnedTimeZone != nil {
clTimeZone = source.pinnedTimeZone
}
if source.pinnedLocation != nil {
clLocation = source.pinnedLocation
}
// I added 2 lines here and it just worked
source.view.removeFromSuperview()
source.removeFromParentViewController()
}