Custom Segue class perform method called, action doesn't do anything - ios

I'm trying to create a custom segue between the ViewController (first view) and the BrowserController (second view).
Currently I have...
CustomSegue.h:
#import <UIKit/UIKit.h>
#interface CustomSegue : UIStoryboardSegue
#end
CustomSegue.m:
#import "CustomSegue.h"
#implementation CustomSegue
- (void)perform {
NSLog(#"Perform Method Running");
UIViewController *ViewController = (UIViewController *) self.sourceViewController;
UIViewController *BrowserController = (UIViewController *) self.destinationViewController;
NSLog(#"Starting duration...");
[UIView transitionWithView:ViewController.navigationController.view duration:0.2
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
NSLog(#"Animation section");
[ViewController.navigationController pushViewController:BrowserController animated:NO];
}
completion:NULL];
NSLog(#"Performance Method Completion");
}
Nothing happens when I click the button to go to the next view.
I set the view segue to "custom" (CTRL drag) and defined my class as "CustomSegue". I see there are two "custom" options to select after CTRL and dragging- I have tried both of these just in case (and I re-defined my class both times), still the problem persists. I also used an NSLog and saw that the perform method is being called, I have no errors, and yet the button still does not perform the segue (or ANY segue) to the next view.
The button that triggers the segue
- (IBAction)browserButton:(id)sender
This is the last area I could narrow it down to... do I need to add anything to this IBAction to tell it to use the new segue?

What Xcode is telling you is precisely right: self (which is of type CustomSegue, of course) has neither ViewController nor BrowserController property. This is because you did not declare these properties in your CustomSegue class, and its base class UIStoryboardSegue does not have them either.
There are two solutions that you could try - using built-in properties directly, or wrapping them in properties with the names that you desire.
Here is the first approach:
UIViewController *ViewController = (UIViewController *) self.sourceViewController;
UIViewController *BrowserController = (UIViewController *) self.destinationViewController;
Here is the second approach:
-(UIViewController*) ViewController {
return self.sourceViewController;
}
-(UIViewController*) BrowserController {
return self.destinationViewController;
}
The first approach is faster to implement, but it may be less readable. The second approach requires more typing, but it gives the source and the destination controllers the names that better describe their roles in your application. The choice is up to you.

You don't need to do anything to the IBAction. It seems like everything should be working fine and you've tried everything- I recommend you delete the work (or rollback if you use github) and start it over again. Start from the point right before you added your CustomSegue class and be sure to retype the code (you never know, sometimes you catch small details).
Goodluck.

Related

Passing data from one ViewController to another when switching tabBarController index instead of segueing

My app contains a tabBarController with two tabs. Tab number 0 contains SearchViewController, tab number 1 contains MatchCenterViewController. What I want to do is set the value of MatchCenterViewControllers didAddNewItem property before switching to that tab. I know that if I had done this with a segue, it would be done as so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowMatchCenterSegue"]) {
MatchCenterViewController *controller = (MatchCenterViewController *) segue.destinationViewController;
self.didAddNewItem = 1;
controller.didAddNewItem = self.didAddNewItem;
}
}
However I don't want to do this with a segue, I want it to simply set didAddNewItem of MatchCenterViewController before calling [self.tabBarController setSelectedIndex:1];. Is it possible to do this?
You should be able to pull the view controller out of [self.tabBarController viewControllers]
A bit of pseudo-code for you to try and mess about with:
UIViewController *toViewController = [self.tabBarController viewControllers][1];
if ([toViewController isKindOfClass:[MatchCenterViewController class]]) { // This is just for the sake of safety. Good practice to get into type checking before you cast.
MatchCenterViewController *matchViewController = (MatchCenterViewController *)toViewController;
matchViewController.didAddNewItem = true; // Really this should be a Bool!
}
[self.tabBarController setSelectedIndex:1];
Best to use a protocol and delegate, this is the cleanest way of passing this data to another view controller. It also decouples your code to make it easier to manage later. For example you build an iPad version with no UITabbarController.
Define a protocol in SearchViewController.h:
//under the #import
#protocol SearchViewControllerDelegate
//define a property to send delegate messages to
#property (nonatomic,strong) id <SearchViewControllerDelegate> delegate;
//after #end
#protocol SearchViewControllerDelegate <NSObject>
- (void)searchViewControllerDidSearchWithResult:(NSArray *)result;
Then when you are setting up the view controllers assign MatchViewController to be the SearchViewControllers delegate.
//in SearchViewController.m send the delegate message
[self.delegate searchViewControllerDidSearchWithResult:arr];
Then in MatchViewController adopt the protocol to get messages.

How to change a UILabel one one View Controller from another View Controller?

I am relatively new to Xcode and have tried to find the answer by searching, without luck.
My app has 5 View Controllers, V1 through V5, which are embedded in one Tab Bar Controller. Each View Controller has a segue to one and the same Setup Menu View Controller. The Menu changes some labels on the View Controllers. I use a delegate to make sure that the View Controller that calls the Menu gets updated with the new settings when you leave the Menu. However, this allows me to modify only the labels on the View Controller that called the Menu Controller, not on the 4 other ones.
I work form a Story Board. Is there a simple way to set the UILabels on V2, V3, V4 and V5 from V1 (and vice versa), or even better, set the labels on V1 through V5 from the Menu View Controller (which is not embedded in the Tab Bar Controller)?
I have seen something that could help here, but this seems rather complicated for what I want. The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
This sounds like a good time for NSNotificationCenter. You are going to have your MenuViewController generate a notification with the new data that should be updated in your other view controllers:
// User has updated Menu values
[[NSNotificationCenter defaultCenter] postNotificationName:#"MenuDataDidChangeStuffForLabels" object:self userInfo:#{#"newLabelValue" : labelText}];
In your V1, V2, etc. you can add subscribe to these notifications using this code in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
// Subscribe to NSNotifications named "MenuDataDidChangeStuffForLabels"
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabelText) name:#"MenuDataDidChangeStuffForLabels" object:nil];
}
Any object that subscribes using that code will call the updateLabelText method anytime a notification with that name is posted by the MenuViewController. From that method you can get the new label value and assign it to your label.
- (void)updateLabelText:(NSNotification *)notification {
NSString *newText = notification.userInfo[#"newLabelValue"];
myLabel.text = newText;
}
What I would do is subclass the tab bar controller and set that as the delegate for the menu view controller. From there, you can get updated when the labels are supposed to change and then communicate with the 5 tabs and update the labels.
Alternatively, you could use NSNotifications to let all the 5 view controllers know when settings change.
Lastly, you could add the menu settings to a singleton and have all of the view controllers observe the various properties that can change.
The label changes I need are quite simple and are all predefined. Is there a method that is called every time you switch tabs in a tabbed application? Similar to ViewDidLoad?
Regarding this question, the methods you're looking for are viewWillAppear: and viewDidAppear.
Here is a very simple solution if your workflow is also simple. This method changes all the labels from the different ViewControllers directly from what you call the Menu ViewController.
Let's say you have the following situation :
The blue ViewController is of the FirstViewController class. The green ViewController is of the SecondViewController class. The labels on each of those are referenced by the properties firstVCLabel and secondVCLabel (on the appropriate class' header file). Both these ViewControllers have a "Modal" button which simply segues modally on touch up inside.
So when you clic on any of these two buttons, the orange ViewController (of ModalViewController class) is presented. This ViewController has two buttons, "Change Label" and "Back", which are linked to touch up inside IBActions called changeLabel: and back:.
Here is the code for the ModalViewController :
#import "ModalViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#interface ModalViewController ()
#end
#implementation ModalViewController
// Action linked to the "Change Label" button
- (IBAction)changeLabel:(id)sender {
// Access the presenting ViewController, which is directly the TabBarController in this particular case
// The cast is simply to get rid of the warning
UITabBarController *tabBarController = (UITabBarController*)self.presentingViewController;
// Go through all the ViewControllers presented by the TabBarController
for (UIViewController *viewController in tabBarController.viewControllers) {
// You can handle each ViewController separately by looking at its class
if ([viewController isKindOfClass:[FirstViewController class]]) {
// Cast the ViewController to access its properties
FirstViewController *firstVC = (FirstViewController*)viewController;
// Update the label
firstVC.firstVCLabel.text = #"Updated first VC label from Modal";
} else if ([viewController isKindOfClass:[SecondViewController class]]) {
SecondViewController *secondVC = (SecondViewController*)viewController;
secondVC.secondVCLabel.text = #"Updated second VC label from Modal";
}
}
}
// Action linked to the "Back" button
- (IBAction)back:(id)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
For the sake of completeness, here are FirstViewController.h :
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *firstVCLabel;
#end
And SecondViewController.h :
#import <UIKit/UIKit.h>
#interface SecondViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *secondVCLabel;
#end
There is no relevant code in the implementation of these classes.
Thanks a lot guys, I am impressed by your quick responses. In this particular case, viewWillAppear does the trick:
- (void)viewWillAppear:(BOOL)animated
{ [self AdaptLabels];
NSLog(#"View will appear.");
}
Every time a new tab is chosen, it updates the labels in the new View, according to a global variable set by the Menu, just before they appear. Very quick and clean. Thanks to all of you!

How to access parent view controller if there is container view inbetween?

How can I access Tab VC from rightmost VC(black)? I tried to use parentViewController from it but got nil.
I'm not a great fan of Containers, they really slow down the storyboard management in XCode.
You should be able to achieve the same result by turning all containers in simple views with a common IBOutlet to some kind of BaseViewController (you should always extend your custom BaseViewController instead of UIViewController in your classes, it gives you more flexibility for common features. Maybe you're already doing it :) ).
Then you can create a custom segue class with a perform method like this
-(void) perform {
BaseViewController* source = (BaseViewController*) self.sourceViewController;
UIViewController* destination = self.destinationViewController
[source.containerView addSubview:destination];
[source addChildViewController:destination];
//Custom code for properly center the destination view in the container.
//I usually use FLKAutolayout for autolayout projects with something like this
//[destination.view alignToView:source.view];
}
Draw a manual segue for the parent view controller to the "contained" view controller an give it a common identifier (something like "containerSegue").
Then in each view container view controller viewDidLoad method add:
[self performSegueWithIdentifier:#"containerSegue" sender:self];
and you should be in the same situation as before.
The only difference is that you can tweak the CustomSegue by adding custom properties and configuration for destination view controller. And, thanks to addChildViewController, your child VC should now have a parentViewController.
And, most of all, your storyboard should be REALLY smoother and faster to load in XCode.
Try this in rootViewController,
rootViewController.h
#interface rootViewController: UIViewController
{
}
+ (UIViewController *) sharedRootViewController;
#end
rootViewController.m
#import "rootViewController.h"
#implementation rootViewController
+ (UIViewController *) sharedRootViewController
{
return (UIViewController *)((UIWindow *)[[[UIApplication sharedApplication] windows] objectAtIndex:0]).rootViewController;
}
- (void) viewDidLoad
{
}
.
.
.
#end

Reloading data for collectionview

I have 2 ViewControllers
ViewControllerWithCollectionView (FIRST) and ModalViewControllerToEditCellContent (SECOND)
I segue from FIRST to SECOND modally. Edit cell. Return.
After dismissing SECOND controller, edited cell doesn't get updated until i call
[collection reloadData]; somewhere manually.
Tried to put it in viewWillAppear:animated:, when i check log, it's not called (after dismissing SECOND)
I've tried various solutions, but i can't brake thru (maybe I'm just too exhausted). I sense that I'm missing something basic.
EDIT dismiss button
- (IBAction)modalViewControllerDismiss
{
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
//tried this also
CBSStickerViewController *pvc = (CBSStickerViewController *)self.stickerViewController;
//tried passing reference of **FIRST** controller
[pvc.cv reloadData];//called reloadData
//nothing
[self dismissViewControllerAnimated:YES completion:^{}];
}
It's tough to tell from the posted code what's wrong with the pointer to the first view controller that you passed to the second. You should also be able to refer in the second view controller to self.presentingViewController. Either way, the prettier design is to find a way for the first view controller to learn that a change has been made and update it's own views.
There are a couple approaches, but I'll suggest the delegate pattern here. The second view controller can be setup to have the first view controller do work for it, namely reload a table view. Here's how it looks in almost-code:
// SecondVc.h
#protocol SecondVcDelegate;
#interface SecondVC : UIViewController
#property(weak, nonatomic) id<SecondVcDelegate>delegate; // this will be an instance of the first vc
// other properties
#end
#protocol SecondVcDelegate <NSObject>
- (void)secondVcDidChangeTheSticker:(SecondVc *)vc;
#end
Now the second vc uses this to ask the first vc to do work for it, but the second vc remains pretty dumb about the details of the first vc's implementation. We don't refer to the first vc's UITableView here, or any of it's views, and we don't tell any tables to reload.
// SecondVc.m
- (IBAction)modalViewControllerDismiss {
self.sticker.text = self.text.text; //using textFields text
self.sticker.title = self.titleText.text;// title
[self.delegate secondVcDidChangeTheSticker:self];
[self dismissViewControllerAnimated:YES completion:^{}];
}
All that must be done now is for the first vc to do what it must to be a delegate:
// FirstVc.h
#import "SecondVc.h"
#interface FirstVc :UIViewController <SecondVcDelegate> // declare itself a delegate
// etc.
// FirstVc.m
// wherever you decide to present the second vc
- (void)presentSecondVc {
SecondVc *secondVc = // however you do this now, maybe get it from storyboard?
vc.delegate = self; // that's the back pointer you were trying to achieve
[self presentViewController:secondVc animated:YES completion:nil];
}
Finally, the punch line. Implement the delegate method. Here you do the work that second vc wants by reloading the table view
- (void) secondVcDidChangeTheSticker:(SecondVc *)vc {
[self.tableView reloadData]; // i think you might call this "cv", which isn't a terrific name if it's a table view
}

didSelectViewController does not get called on certain occasions

I have the problem that many already have reported, didSelectViewController doesn't get called, but in my case it sometimes gets called. I have three tabs and three view controllers. Every time user presses second or third tab I need to execute some code. In my SecondViewController and ThirdViewController I have:
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
[tabBarController setDelegate:self];
Now everything works fine with the SecondViewController, the didSelectViewController gets called every time the second tab is pressed. Also in ThirdViewController didSelectViewControllergets called every time the third tab is pressed but only when second bar is meanwhile not pressed. So when I switch back and forth between FirstViewController and ThirdViewController everything is OK. But when I go in a pattern like first->second->third, then didSelectViewController doesn't get called in ThirdViewController. Also when I go like first->third->second->third didSelectViewController gets called in ThirdViewController the first time but not the second time. Any ideas?
It's hard to follow what exactly you are doing, but from what I understand you are responding to tab switches by changing the UITabBarController's delegate back and forth between SecondViewController and ThirdViewController.
If that is true, I would advise against doing this. Instead I would suggest you try the following:
Assign a delegate that never changes. For a start you could use your app delegate, but it would probably be better if you had a dedicated small class for this. I am sure that now you have a non-changing delegate, it will get 100% of all the calls to tabBarController: didSelectViewController:.
The object that is the delegate must have a reference to both the SecondViewController and ThirdViewController instances. If you are designing your UI with Interface Builder, you might do this by adding two IBOutlets to the delegate class and connecting the appropriate instances to the outlets.
Now when the delegate receives tabBarController: didSelectViewController: it can simply forward the notification to either SecondViewController or ThirdViewController, depending on which of the tabs was selected.
A basic code example:
// TabBarControllerDelegate.h file
#interface TabBarControllerDelegate : NSObject <UITabBarControllerDelegate>
{
}
#property(nonatomic, retain) IBOutlet SecondViewController* secondViewController;
#property(nonatomic, retain) IBOutlet ThirdViewController* thirdViewController;
// TabBarControllerDelegate.m file
- (void) tabBarController:(UITabBarController*)tabBarController didSelectViewController:(UIViewController*)viewController
{
if (viewController == self.secondViewController)
[self.secondViewController doSomething];
else if (viewController == self.thirdViewController)
[self.thirdViewController doSomethingElse];
}
EDIT
Some hints on how to integrate the example code from above into your project:
Add an instance of TabBarControllerDelegate to the .xib file that also contains the TabBarController
Connect the delegate outlet of TabBarController' to the TabBarControllerDelegate instance
Connect the secondViewController outlet of TabBarControllerDelegate to the SecondViewController instance
Connect the thirdViewController outlet of TabBarControllerDelegate to the ThirdViewController instance
Add a method - (void) doSomething to SecondViewController
Add a method - (void) doSomethingElse to ThirdViewController
Make sure that you don't have any code left in SecondViewController and ThirdViewController changes the TabBarController delegate!
Once you are all set and everything is working fine, you will probably want to cleanup a bit:
Change the names of the notification methods doSomething and doSomethingElse to something more sensible
If you followed the discussion in the comments, maybe you also want to get rid of the secondViewController and thirdViewController outlets
I too had this problem and got fed up with it. I decided to subclass UITabBarController and override the following methods. The reason I did both was for some reason on application launch setSelectedViewController: wasn't being called.
- (void)setSelectedIndex:(NSUInteger)selectedIndex
{
[super setSelectedIndex:selectedIndex];
// my code
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController
{
[super setSelectedViewController:selectedViewController];
// my code
}
I just dug through this tutorial on storyboards, and I thought of an alternative to using UITabBarControllerDelegate. If you want to stick to UITabBarControllerDelegate then feel free to ignore this answer.
First, create a subclass of UITabBarController, let's call it MyTabBarController. In the storyboard editor you need to change the "Class" property of the tab bar controller so that the storyboard picks up your new class.
Add this code to MyTabBarController.m
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SecondVC"])
{
SecondViewController* secondViewController = (SecondViewController*)segue.destinationViewController;
[secondViewController doSomething];
}
else if ([segue.identifier isEqualToString:#"ThirdVC"])
{
ThirdViewController* thirdViewController = (ThirdViewController*)segue.destinationViewController;
[thirdViewController doSomethingElse];
}
}
In the storyboard editor, you can now select the two segues that connect to SecondViewController and ThirdViewController and change the segue identifier to "SecondVC" and "ThirdVC", respectively.
If I am not mistaken, that's all you need to do.

Resources