Swift: Access Parent ViewController Segue from UIScrollView - ios

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];

Related

Refresh FirstView from SecondView in Tab Bar Controller

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.

How to segue to another view controller when a subview with a gesture is tapped?

I have a view controller with a subview: gestureView that is referenced by the view controller via an outlet. I have assigned a tap gesture from viewDidLoad of the parent view controller to gestureView like:
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self.gestureView action:#selector(handleTap)];
[self.gestureView addGestureRecognizer:tapGesture];
gestureView uses a custom class where handleTap: is implemented and it works fine, I can log from gestureView's handleTap:, however how can I perform a segue from the current view controller to another view controller when gestureView is tapped.
I understand that there may be easier ways to do this, but this works best for my situation.
You can use Delegate pattern or Notification pattern.
Both will work in your case, As -handleTap() method is implemented in your subview you can create protocol in your subview class.
And when you get handleTap method you can call delegate method which should be implemented in your parent class.
In your subview class Above class declaration define protocol
#protocol SubViewClassDelegate <NSObject>
#required;
- (void) subviewDidTapped:(id) sender;
#end
Define property of delegate.
#property (nonatomic, unsafe_unretained) id<SubViewClassDelegate> delegate;
In implementation file of subview's class call delegate method.
- (void) handleTap {
if ([self.delegate respondsToSelector:#selector(subviewDidTapped:)]) {
[self.delegate subviewDidTapped:yourObject]; // in yourObject you can pass data if require.
}
}
In your parent class implemented delegate of subview class
#interface ParentViewController : UIViewController <SubViewClassDelegate>
In viewDidLoad
self.gestureView.delegate = self;
And Implement the delegate method of Subview class in Parent controller.
-(void) subviewDidTapped:(id)sender {
// Navigate using performSegueWithIdentifier:
[self performSegueWithIdentifier: #"segueIdentifier" sender: self];
}
In the handleTap, perform a segue to the other view controller you intend to display next.
[self performSegueWithIdentifier: #"YOU_SEGUE_IDENTIFIER" sender: self];
The segue that you will perform is the segue in the storyboard that is from current view controller to the next view controller. (You need to create this).
You can pass parameters on to the next view controller in the prepareForSegue:sender:, once you identify that it is the correct sender by checking the segue identifier.

Update all view controllers of a UITabBarController

I have a UITabBarController with four tabs. In each of the view controllers presented when a tab is selected I have a reset button. Tapping the button will change the appearance of all the view controllers. In particular, it will change the text of some labels in the different view controllers.
Is there some recommended way to update all the view controllers of a UITabBarController at the same time i.e. to make them reload their views?
My current approach is to make those view controllers conform to a protocol
#protocol XYReloadableViewController
- (void)reloadContents;
#end
and then send the message -reloadContents to all the view controllers when the button is tapped:
- (IBAction)touchUpInsideResetButton {
// ...
NSArray *viewControllers = self.tabBarController.viewControllers;
for (UIViewController<XYReloadableViewController> *viewController in viewControllers) {
[viewController reloadContents];
}
}
Then in each of the view controllers I would have to implement that method:
- (void)reloadContents {
[self.tableView reloadData];
// other updates to UI ...
}
But that seems a little too complicated. So is there an easier way to tell the view controllers to reload their views?
Edit: And what happens if I present a UINavigationController in some of the tabs or a container view controller? I would need to pass the message along the chain of all its child view controllers...
You can create ReloadViewController and all you contrlollers inheritance
from him.
ReloadViewController have property UIButton and methods:
-(void)reloadContents;
-(IBAction)touchUpInsideResetButton:(id)sender;
in .m file:
-(void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(reloadContents)
name:#"MyNotification"
object:nil];
}
- (IBAction)touchUpInsideResetButton:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyNotification"
object:nil];
}
in your viewControllers need only override method reloadContents
Notifications sound like a better fit for this. When view controllers need to be reset, broadcast an NSNotification and have any view controllers that might need to reset themselves listen for that notification, and trigger what they need to do. That way it doesn't matter how far down a navigation stack they are.
You might want to defer updates until the view actually appears. You could set a BOOL needsUpdate when the VCs receive the notification, but only do the actual update in viewWillAppear:, to save resources and prevent a large number of updates from going off at once (and perhaps blocking the main thread).
If this behaviour is common to all your view controllers, make a UIViewController subclass to prevent repeating code and have them all inherit from that. Alternatively, (if you're using Apple VC subclasses) make a category on UIViewController to add the notification methods.

Overriding drawRect method in a UIView subclass & call it into another UIViewcontroller

I am a novice ios programmer and this is my first project.I am going to develop this project specifically for ipad. In this project i need to draw several circles & display data on it by parsing xml element.I have done the the circle drawing part by subclassing UIView class and overriding drawRect method. I load the UIView subclass in a UIViewcontroller via loadview method .Now what i need to do is
Touch a circle and switch to another UIViewcontroller.
I am not sure how to switch to another UIViewcontroller because all of drawing and touch detecting code is in my UIView subclass.
A help will be appreciated
You need to use a delegate method to tell the parent view controller that there was a touch, so it can present another view controller.
At the top of your UIView subclass header, add this:
#protocol MyCustomViewDelegate <NSObject>
- (void)customViewCircleTapped;
#end
Then, in your declaration of the view (the existing declaration you have of your custom view subclass):
#interface MyCustomView : UIView
...
#property (weak) id<MyCustomViewDelegate> delegate;
After that, in your view controller, you need to set view.delegate = self, so the view can reference the view controller.
Then, in your view controller header, change your declaration to look like this:
#interface MyViewController : UIViewController <MyCustomViewDelegate>
then implement customViewCircleTapped in the view controller implementation:
- (void)customViewCircleTapped {
... // Open a view controller or something
}
Once you have done that, in the touch detection code in your view, you can add:
[self.delegate customViewCircleTapped];
What this does is gives your custom view the ability to tell its parent view controller that something has happened, by calling this method (you can change it and add arguments if you need to pass data), and then the view controller can open another view controller or perform some action based on this.
View detects touches → Process touches → call customViewCircleTapped delegate method on view controller → view controller opens another view controller
Provide the subclassed view with a delegate notifying the view is touched and in the delegate call in the mainVC do the push job
The way I like to do this is to send a message up the responder chain. This completely decouples the view from it's enclosing views and view controllers. So, when your circle view is tapped, it emits a "circle view was tapped message" to the responder chain--the first object on the responder chain that responds to that message (which don't know/care which one) will have it invoked. It's simple to implement.
You attach a UITapGestureRecognizer to your view with -tapped: as the action.
On your view, your tap action might look like this.
-(IBAction)tapped:(UIGestureRecognizer*)g
{
[ self sendAction:#selector( circleViewTapped: ) withObject:self ] ;
}
The -sendAction: method on UIResponder is added via a category, like this:
#implementation UIResponder (ActionSending)
-(void)sendAction:(SEL)action withObect:(id)object
{
UIResponder * target = self ;
while( self && ![ target respondsToSelector:action ] )
{
target = [ target nextResponder ] ;
}
[ target performSelector:action withObject:object ] ;
}
#end
Your view controller or any parent view or parent view controller that responds to circleViewTapped: will have that method invoked when your circle view is tapped.
A note about organizing your views:
I would make a circle view UIView subclass. Instantiate your subclass for each circle to be displayed. To each of those attach a UITapGestureRecognizer. The target of your gesture recognizer is the -tapped: method, above.
The current answers are overcomplicating the solution. You don't need gesture recognisers or delegate protocols.
Make your circle drawing view a subclass of UIControl instead of UIView. Then attach your view controller as a target to the UIControlEventTouchUpInside event:
[circleView addTarget:self action:#selector(circleTapped:) forControlEvents:UIControlEventTouchUpInside];
This will call the circleTapped: method on your view controller, with the tapped view as the sender.

UIViewController -dealloc method not called

I am working with Automatic Reference Counting.
I have a custom UIViewController subclass and whenever I call -presentViewController: animated:completion: or remove its view from the superview I would like to NSLog something like "I am dealloced" so I know that the view controller has successfully been removed. I have implemented the -dealloc method in my view controller. However I started a test project where I just had two UIViewController instances (no retain cycles) and -dealloc is not called either when I push the second UIViewController modally or when I remove the superview or when I remove it from the parent view controller. Am I missing something ? In my original project (not the test case) Instruments shows me that those controllers leave a memory footprint that I can't get rid off.
If you want to switch view controllers, and have the one you're switching away from be deallocated, then just switch the root view controller of the window. So, if you're in VC1 and want to go to VC2, then do this in VC1:
VC2 *vc2 = [[VC2 alloc] init]; // or however else is appropriate to get an instance of this class
self.view.window.rootViewController = vc2;
If you haven't created any property to point to vc1, then it will be deallocated after making this switch.
If you want to use a modal presentation or a modal segue (to get the animation when you switch controllers), you can still get the initial controller to be deallocated by switching the root view controller after the presentation from the viewDidAppear method of vc2:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.window.rootViewController = self;
}
To get a print when the View Controller is deallocated you can implement the dealloc method as
- (void) dealloc {
NSLog(#"The instance of MyViewController was deallocated");
}
Then to get a print when the View Controller left the view you can implement
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(#"The instance of MyViewController left the main view")
}
If you use -presentViewController:animated:completion: you are retaining the parentViewController every time you call this method. ModalViewControllers are simply pushed on top of the other ViewController.
ModalViewControllers are only designed for some kind of information / User Input and stuff like that. If you want to dealloc the ParentViewController you have to deal with your own implementation.
dealloc method isn't called when the class is retained (or something in this class is retained) and not reeleased. It is justly for projects with both ARC and without it. So check your code twice.

Resources