Pass sender of programmatically called segue to destination - ios

Sorry if the title isn't very clear, but hopefully I can elaborate here.
I have a ViewController MatchLineupViewController, which displays 22 buttons to represent rugby players on a team. When the user taps any of these buttons, a modal segue is called programmatically in the following method:
- (IBAction) showSquadSelector:(UIButton *)sender {
[self performSegueWithIdentifier:#"SeguePopupSquad" sender:sender];
}
The modal ViewController which is then displayed is called SquadSelectViewController. It passes back a selected player object to the MatchLineupViewController, which is acting as a delegate. This works perfectly.
However, I want to assign the profile_picture attribute of the returned object to the UIButton that sent the segue in the first place.
EDIT - The returned object is an NSDictionary as shown in the following code:
- (void) selectPlayer:(NSDictionary *)player forButton:(UIButton *)sender {
[sender.imageView setImage:[UIImage imageWithContentsOfFile:[player objectForKey:#"profile_picture"]]];
}
How would I go about doing this? If you require any further code to understand what I am asking, I can provide it.
Many thanks,
Chris
EDIT -
- (IBAction) showSquadSelector:(UIButton *)sender {
[self performSegueWithIdentifier:#"SeguePopupSquad" sender:sender];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SeguePopupSquad"]) {
SquadSelectViewController *ssvc = (SquadSelectViewController *) segue.destinationViewController;
ssvc.myDelegate = self;
ssvc.senderButton = sender;
}
}
- (void) selectPlayer:(NSDictionary *)player forButton:(UIButton *)sender {
[sender.imageView setImage:[UIImage imageWithContentsOfFile:[player objectForKey:#"profile_picture"]]];
NSLog(#"%#", [player description]);
NSLog(#"%#", [sender description]);
}

You can forward the sender of your showSquadSelector: method to the segue, like this:
[self performSegueWithIdentifier:#"SeguePopupSquad" sender:sender];
The sender of the segue would be the button that triggered the segue, so the code triggered from the segue would know what button has triggered it: your prepareForSegue: would have the correct UIButton. You can now add it to the returned dictionary at a predetermined key (say, #"senderButton") and examine it upon the return from the segue.

Related

AVAudioPlayer is not working in another viewcontroller

I have passed button tag to another Viewcontroller.
It's passed but when I'm calling any another method like play audio play on that selected button, player not working...
I have tried below code :
It passes id of button clicked to the other view. Where I build player and and based on button id I want to play a poem on other view and control functions like volume control, progressbar, duration, play, push, next, etc...
While I'm giving a method to a particular button id to play poem it is not working...
This below code for play a poem in another view where I build player.
`
- (IBAction)btnpoemclicked:(id)sender {
btnPressed = [NSString stringWithFormat:#"%li", (long)[sender tag]];
NSLog(#"selected poem -%#",btnPressed);
PlayerController *pl=[[PlayerController alloc] init];
pl.btnPressed=btnPressed;
[pl setBtnPressed:pl.btnPressed];
[self performSegueWithIdentifier:#"player" sender:sender];
}
You should pass values in prepareForSegue when using Segues.
Update your code like
- (IBAction)btnPoemClicked:(id)sender {
btnPressed = [NSString stringWithFormat:#"%li", (long)[sender tag]];
[self performSegueWithIdentifier:#"player" sender:self];
}
And then In prepareForSegue pass the values to destinationViewController like this
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"player"]) {
PlayerController *playerController = (PlayerController *)segue.destinationViewController;
playerController.btnPressed = btnPressed;
}
}
Implement your button action like,
- (IBAction)buttonPoemClicked:(id)sender {
[self performSegueWithIdentifier:#"Player" sender:sender];
}
Pass your value by implementing prepareForSegue,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Player"]) {
PlayerController *playerController = (PlayerController *)segue.destinationViewController;
playerController.pressedButtonTag = [NSString stringWithFormat:#"%li", (long)[sender tag]];
}
}
Additional Note : Try to make your variable/class names little more understandable or try to follow apple recommended naming conventions.

Multiple Buttons with a Single Segue

I have four buttons and one segue. When any of these buttons get touch up inside event, it will load respective images on the destination view controller. For that, I am recognizing buttons through their tag property [each button (0,1,2,3)]. Here is my code:
- (IBAction)Session1Btn:(id)sender {
[self performSegueWithIdentifier:#"isSection" sender:self];
}
- (IBAction)Session2Btn:(id)sender {
[self performSegueWithIdentifier:#"isSection" sender:self];
}
- (IBAction)Session3Btn:(id)sender {
[self performSegueWithIdentifier:#"isSection" sender:self];
}
- (IBAction)Session4Btn:(id)sender {
[self performSegueWithIdentifier:#"isSection" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SectionViewController *sVC=[segue destinationViewController];
if([segue.identifier isEqualToString:#"isSection"])
{
// the following line gives an error!
NSInteger tagIndex = [(UIButton *)sender tag];
[sVC setSelectedButton:[NSNumber numberWithInteger:tagIndex]];
}
}
However I am getting the following error:
You must have written UIButton UITouchUpInside event like this:
- (IBAction)buttonPressed:(id)sender
{
[self performSegueWithIdentifier:#"isSection" sender:sender];
}
Same way for all your buttons.
Now add one variable UIButton *selectedButton in .h file. and update your button event methods like below:
- (IBAction)buttonPressed:(id)sender
{
selectedButton = (UIButton *)sender;
[self performSegueWithIdentifier:#"isSection" sender:sender];
}
And now you have selectedButton reference, which can be used in other methods. So that,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(UIButton *)sender {
SectionViewController *sVC=[segue destinationViewController];
if([segue.identifier isEqualToString:#"isSection"])
{
[sVC setSelectedButton:[NSNumber numberWithInteger: selectedButton.tag]];
}
}
Swift
Create property for the selected button:
var selectedButton: Int = Int()
In each IBAction assign selectedButton to either 1 or 2 and add performSegue(withIdentifier: "yourSegueIdentifier", sender: self).
In prepare for segue add the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if buttonTapped == 1 {
// what ever happens when first button selected
} else if buttonTapped == 2 {
// what ever happens when second button selected
}
}

I get no animation when I perform the segue programmatically

I have a button that will segue to another View Controller. In shouldPerformSegueWithIdentifier I check the identifier and returns NO for this certain button after calling my own method nextClicked.
In nextClicked I do a HTTP Request and check some stuff, and then if everything is OK I do
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"getstartednext" sender:self];
});
The problem is that I get no animation now... The segue is of type modal. If I return YES in shouldPerformSegueWithIdentifier and don't to the segue programmatically later, the animation appears and everything is as it should.
You are misusing shouldPerformSegueWithIdentifier. If you want to do something special in response to the button click, use action-target and not a segue connected to the button.
I created a new project with your idea and I've got animation.
This is the action connected through IB with the next viewController and with identifier: #"getstartednext"
- (IBAction)go:(id)sender
{
}
Same code you have except sender is nil
- (void) nextClicked
{
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"getstartednext" sender:nil];
});
}
if sender != nil, (IBAction go:) then I'm calling nextClicked because returning NO
else the code is doing the segue because returning YES
- (BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
if ([identifier isEqualToString:#"getstartednext"] && sender != nil)
{
[self nextClicked];
return NO;
}
return YES;
}
Works like a charm.

Dealloc is called on UIViewControllers which are stored in a NSMutableDictionary and presented by UIViewControllerContainment

I've built a custom UITabBarController with Storyboards/Segues and UIViewController containment. Here is a link to it: https://github.com/mhaddl/MHCustomTabBarController
The UIViewControllers which will be presented by the Container are stored in a NSMutableDictionary (keys are the segues' identifiers). Everything is working fine until the point is reached where I come back to a earlier presented ViewController. At this moment "dealloc" gets called on this ViewController before it is presented.
How can I prevent "dealloc" from getting called so it can be used to unsubscribe from Notifications, and nil delegates.
MHCustomTabBarController:
#implementation MHCustomTabBarController {
NSMutableDictionary *_viewControllersByIdentifier;
}
- (void)viewDidLoad {
[super viewDidLoad];
_viewControllersByIdentifier = [NSMutableDictionary dictionary];
}
-(void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.childViewControllers.count < 1) {
[self performSegueWithIdentifier:#"viewController1" sender:[self.buttons objectAtIndex:0]];
}
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.destinationViewController.view.frame = self.container.bounds;
}
#pragma mark - Segue
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (![segue isKindOfClass:[MHTabBarSegue class]]) {
[super prepareForSegue:segue sender:sender];
return;
}
self.oldViewController = self.destinationViewController;
//if view controller isn't already contained in the viewControllers-Dictionary
if (![_viewControllersByIdentifier objectForKey:segue.identifier]) {
[_viewControllersByIdentifier setObject:segue.destinationViewController forKey:segue.identifier];
}
for (UIButton *aButton in self.buttons) {
[aButton setSelected:NO];
}
UIButton *button = (UIButton *)sender;
[button setSelected:YES];
self.destinationIdentifier = segue.identifier;
self.destinationViewController = [_viewControllersByIdentifier objectForKey:self.destinationIdentifier];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([self.destinationIdentifier isEqual:identifier]) {
//Dont perform segue, if visible ViewController is already the destination ViewController
return NO;
}
return YES;
}
#pragma mark - Memory Warning
- (void)didReceiveMemoryWarning {
[[_viewControllersByIdentifier allKeys] enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
if (![self.destinationIdentifier isEqualToString:key]) {
[_viewControllersByIdentifier removeObjectForKey:key];
}
}];
}
#end
MHTabBarSegue:
#implementation MHTabBarSegue
- (void) perform {
MHCustomTabBarController *tabBarViewController = (MHCustomTabBarController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) tabBarViewController.destinationViewController;
//remove old viewController
if (tabBarViewController.oldViewController) {
[tabBarViewController.oldViewController willMoveToParentViewController:nil];
[tabBarViewController.oldViewController.view removeFromSuperview];
[tabBarViewController.oldViewController removeFromParentViewController];
}
destinationViewController.view.frame = tabBarViewController.container.bounds;
[tabBarViewController addChildViewController:destinationViewController];
[tabBarViewController.container addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:tabBarViewController];
}
#end
"At this moment "dealloc" gets called on this ViewController before it is presented." -- no, not really. Dealloc is being called on a controller that never gets on screen, not the one you came from initially or are going back to. The way your segue is set up, and the fact that you keep a reference to your controllers in the dictionary, means that they never get deallocated. Segues (other than unwinds) ALWAYS instantiate new view controllers, so what's happening is that a new instance of, say VC1 is created when you click on the first tab (and a segue is triggered), but you never do anything with that controller (which would be self.destinationViewController in the custom segue class) so it's deallocated as soon as the perform method exits.
Depending on where you setup any delegates or notification observers, this might not be a problem -- this controller that's created, and then immediately deallocated never has its viewDidLoad method called, so if you do those things in viewDidLoad, they won't ever happen for this transient view controller.
If you don't want this to happen, then you need to make your transitions in code without using segues.

Trying to implement a delegate that signals when the back button is pressed in the navigation bar - what am I doing wrong?

I have a main list of articles, and upon clicking one it segues to a reading view controller, and there I keep track of the progress of the user's reading with that view controller having an NSNumber property holding the position. I want to update this position back to the root view controller when they press the back button (so I can show them their progress) but my delegate doesn't seem to be working.
In the reading view's .h file:
#property (nonatomic, weak) id<UpdateProgressDelegate> delegate;
...
#protocol UpdateProgressDelegate <NSObject>
#required
- (void)finishedReadingWithPosition:(NSNumber *)position;
#end
In the .m file:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
In my root view (note it does indeed implement the protocol):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ReadBasicArticleSegue"] || [segue.identifier isEqualToString:#"ReadFullArticleSegue"]) {
ReadingViewController *destination = segue.destinationViewController;
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
self.rowOfLastSelectedCell = #(indexPath.row);
Article *article = self.articles[[self.rowOfLastSelectedCell intValue]];
// Set ReadingViewController's variables so the selected article can be read
destination.textToRead = [article.body componentsSeparatedByString:#" "];
destination.wordsPerMinute = #(1500);
destination.numberOfWordsShown = #(3);
destination.delegate = self;
}
}
and...
- (void)finishedReadingWithPosition:(NSNumber *)position {
Article *article = [self.articles objectAtIndex:[self.rowOfLastSelectedCell intValue]];
article.position = position;
[self.tableView reloadData];
}
I just don't see what I'm doing wrong. When I press the back button, the root view controller still has a 0% progress indicator.
Here:
- (void)viewDidDisappear:(BOOL)animated {
[super viewWillDisappear:YES];
if ([self.delegate respondsToSelector:#selector(finishedReading:)]) {
[self.delegate finishedReadingWithPosition:self.position];
}
}
viewDidDisappear: should pass the same 'did' method to super, not viewWillDisappear: ..
[super viewDidDisappear:animated];
The selector finishedReading: is not the same as the selector finishedReadingWithPosition:. As it is not implemented in the delegate, the conditional is not called.
Typos - or the solution...?
two quick things:
first, in your delegate respondstoSelector check, make sure you are testing for the correct method. You have "finishedReading:" in the check, then you call finishedREadingWithPosition:" in the method call. My guess is that it's skipping that line because its the wrong selector your checking for.
next make sure self.position have a value
and also:
I agree on the [super] call that #He Was mentioned - needs to be a call for the same method

Resources