The issue I am having is that I want to call a method in a navigationController by clicking a button inside a UIView (footer). When I press the button, it should call the method I'm trying to access to open the Video recorder in the code below.
I was told I could implement a delegate method or use a NSNotification. Below is what I have:
My footer (ESPhotoDetailsFooterView.m) has my button that I created. My footer only contains a UIView.
The method I'm trying to access in my footer resides in (ESTabBarController.m)
This is what I am trying to trigger when pressing my button:
RecorderViewController *viewController = [[RecorderViewController alloc] init];
[viewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self.navController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self.navController pushViewController:viewController animated:NO];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:self.navController animated:YES completion:nil];
});
I am new to Objective C and understand the basics. I cannot figure out what I need to do to accomplish this. Any help would be much appreciated.
The code for the button is as follows:
// Create a standard UIButton programmatically using convenience method
UIButton *camButton = [UIButton buttonWithType:UIButtonTypeCustom];
// Set the location (x,y) and size (width,height) of the button
camButton.frame = CGRectMake(9.0f, 8.0f, 35.0f, 35.0f);
// Create UIImages from image resources in your application bundle
// using convenience methods (no need to release)
UIImage *normal = [UIImage imageNamed:#"BingComm"];
UIImage *highlighted = [UIImage imageNamed:#"BingCommClick"];
// Set the button's background to an image
[camButton setBackgroundImage:normal forState:UIControlStateNormal];
[camButton setBackgroundImage:highlighted forState:UIControlStateHighlighted];
// Add the target-action for the touch event
#pragma GCC diagnostic ignored "-Wundeclared-selector"
[camButton addTarget:self action:#selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.mainView addSubview:camButton];
Yes, in this case delegation is a good choice, basically you will need a delegate object in your footer view. Then you set the delegate to TabBarController at runtime.
When user clicks the button, call you delegate with your method [delegate method]
This is a very important design pattern objective-c, it would be very helpful if you can follow this tutorial to fully understand delegation.
http://www.alexefish.com/post/522641eb31fa2a0015000002
I agree that delegation is important and very much worth learning, but another way to solve the same problem would be to the following:
In your method definition for btnClicked, call the following code
if([self tabBarController]) {
[[self tabBarController] methodToCall];
}
Since your view controller should be embedded within a tab bar controller, it will have this property set. You can then call any public methods contained within the tabBarController class.
Related
I am trying to build a simple library with working UIElements. What I am trying to do is, creating UIViewController objects from one class instances and push that new ViewController on the current VC Stack with the presentViewController method.
I can see that the UIElements has been successfully adding on the stack, but GestureRecognizer and UIButton's target does not work. When I am checking on ViewDebug, these settings are <NSNull null>.
This is my class method which I am creating the UI and putting on the current view stack.
-(void)displayAd{
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:fullpageCampaign.mainImage]];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
fullPageView = [[UIViewController alloc] init];
fullPageView.view.frame = CurrentVC.view.bounds;
fullPageView.view.backgroundColor = [UIColor redColor];
UIImageView *staticImageView = [[UIImageView alloc] init];
staticImageView.frame = CurrentVC.view.frame;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected)];
singleTap.numberOfTapsRequired = 1;
[staticImageView addGestureRecognizer:singleTap];
staticImageView.userInteractionEnabled = YES;
[fullPageView.view addSubview:staticImageView];
staticImageView.image = [UIImage imageWithData:imageData];
[CurrentVC.view addSubview:fullPageView.view];
//[fullPageView didMoveToParentViewController:self];
[CurrentVC presentViewController:fullPageView animated:YES completion:^{
NSLog(#"Tagon Ads is about to showing.");
UIButton *closeButton = [self createButtonWithAssetName:#"tagonAssets.bundle/close_button" TargetMethod:#"closeModal" andView:staticImageView];
[staticImageView addSubview:closeButton];
[CurrentVC.view bringSubviewToFront:closeButton];
}];
});
});
}
CurrentVC is the current viewController that I am sending as a parameter through my library's method in order to add a new viewController stack on to it.
Where is closeModal action? Probably same class as your currentVC. If so, your closeButton referenced currentVC but you already gone to fullPageView from there. So, your button lost his reference.
Just create new controller, send imageData there, create custom initializer, create new UIImageView and UIButton in there. With this way, your button gonna be reference own root and your problem should be solved.
There are several problems with your code, but first of all, I would recommend a different approach to accomplish what you want. As you can see below, using an instance of UIViewController is not the recommended way. Instead, use a storyboard to set up your view controller and it's components. Your code will be much smaller and your design will be easy to understand and change.
You can read more about UIViewController here
You rarely create instances of the UIViewController class directly.
Instead, you create instances of UIViewController subclasses and use
those objects to provide the specific behaviors and visual appearances
that you need.
Here is another potential. Is fullpageCampaign.mainImage residing remotely or locally? If remotely located, then you should consider changing to NSURLSession instead.
Read more about NSData:dataWithContentsOfURL here
Do not use this synchronous method to request network-based URLs. For
network-based URLs, this method can block the current thread for tens
of seconds on a slow network, resulting in a poor user experience, and
in iOS, may cause your app to be terminated.
Another minor thing is that you add the button to the image view. While this is OK, and might work when you allow user interaction for the image view, a cleaner way to do it is to create a UIView container to hold the image view and the button. The container can then also be the view that you attach the tap gesture recognizer to. That way, the image view can stay as a pure image.
The storyboard approach
First, create a sub-class of UIIViewController. It should look something like this:
FullPageViewController.h
#import <UIKit/UIKit.h>
#interface FullPageViewController : UIViewController
- (void)setImage:(UIImage *)adImage;
#end
FullPageViewController.m
#import "FullPageViewController.h"
#interface FullPageViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *adImageView;
#end
#implementation FullPageViewController
- (void)setImage:(UIImage *)adImage {
self.adImageView.image = adImage;
}
- (IBAction)tappedOnAd:(UIGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
// Do your ad thing here
}
}
- (IBAction)closeButtonPressed:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Second, create a storyboard and add your ad view controller to it. Then add an image view, a button, and a tap gesture recognizer to your view controller. The tap gesture recognizer should be dropped on the image view to capture taps from there. You pull all of these objects from the Object Library down right. Also remember to enable user interaction for the image view. There is a property for that on the property page.
You should now have something that looks like this:
Notice the class name top right which should be the name of your new view controller class you just created. Also notice the storyboard ID adVC which you need when instantiating the view controller from code.
The next step is to connect the objects. Select the image view, then drag from the outlet (the ring) under Referencing Outlets to the view controller icon (the yellow icon with a square in) located on top of the view controller window, and select adImageView. The gesture recognizer should already be connected, if you dropped it on the image view when you placed it previously.
Next, connect the action for the close button. Drag from the Touch Up Inside outlet to the view controller icon (the yellow one), and select the closeButtonPressed: method.
Next, connect the tap gesture recognizer to your code. Select it from the list on the left, then drag from Sent Actions to the view controller icon and select tappedOnAd:.
Finally, your code to show the ad looks something like this. This method belongs in your parent view controller.
-(void)displayAd{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:fullpageCampaign.mainImage]];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"AdPage" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"adVC"];
[vc setImage:[UIImage imageWithData:imageData]];
[self presentViewController:vc animated:YES completion:^{}];
});
});
}
I have created a button in a UIView subclass . I need to call popViewControllerAnimated via this button , but nothing work ! and I cannot see the viewController push back to rootViewController . here is my code :
- (void)SomeFunction {
backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[backButton showsTouchWhenHighlighted];
[backButton addTarget:self
action:#selector(backToMainMenu)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)backToMainMenu {
[self.window.rootViewController.navigationController popViewControllerAnimated:YES];
NSLog(#"back");
}
I change the code to this :
UINavigationController *vc = self.window.rootViewController.navigationController;
[vc.navigationController popViewControllerAnimated:YES];
but nothing happens .
I think you need to use the proper target format which takes the button as an argument. So add target function like this:
[backButton addTarget:self
action:#selector(backToMainMenu:)
forControlEvents:UIControlEventTouchUpInside];
and the target should look like this:
- (void) backToMainMenu:(UIButton *) sender{
[self.navigationController popViewControllerAnimated:YES];
}
A better option is to use Delegate pattern because in your current logic you are breaking the MVC architecture and guidelines.
Create a Protocol in your subview class. The receiver of this delegate would be the view controller class from which you are showing your view. In event handling of the button, call the delegate method and from the view controller you would be able to call popViewControllerAnimated successfully.
I believe your fundamental problem (besides design) is in (void)backToMainMenu ... self.window.rootViewController.navigationController will be nil, so this method does nothing
from UIViewController class reference:
If the receiver or one of its ancestors is a child of a navigation controller, this property contains the owning navigation controller. This property is nil if the view controller is not embedded inside a navigation controller.
so you see, the rootViewController cannot be embedded inside a nav controller can it, it is the bottommost one..
why don't you test this:
{
UINavigationController *vc = self.window.rootViewController.navigationController;
if (vc==nil){
NSLog(#"nav controller is nil, this will never work");
}
[vc.navigationController popViewControllerAnimated:YES];
}
I also fully agree with #rory's answer there about design..
PS did you actually create a UINavigationController in order to push this viewController?
I am very new to programming, especially iOS, so any answer should be given as if I am a baby with ADHD.
This is what I have in my buttonclick:
ItemCreateViewController *itemCreateViewConrtoller = [[ItemCreateViewController alloc] init];
[[self navigationController] pushViewController:itemCreateViewConrtoller animated:YES];
What am I missing?
There are a lot of reasons why this could be happening among which is your button is not triggering the intended action. Among the reason why a button could not trigger is your XIB button is not connected to the button object defined in your header and method file.
If you look at the .h file look at all your IBOutlet items there should be a circle on the left most column if it is darkened out it is connected other wise it is not, and you need to connect this in your XIB file.
If this is not the problem please display the code where you specify the selector it should be something like this:
[cancel_button addTarget:self action:#selector(Cancel:) forControlEvents:UIControlEventTouchUpInside];
Make sure that the selector is an IBAction function -
-(IBAction)Cancel:(id)sender {
[self dismissViewControllerAnimated:YES completion:NULL];
return;
}
One other thing, any statement after pushViewController will get executed prior to any actual change in the view. The actual View change only happens once control is returned to IOS. This is significant if your push view controller command is within a long conditional process and you fail to code the return / exit properly.
Since you are using a XIB file for your second view, you need to create it as follows -
ItemCreateViewController *itemCreateViewConrtoller = [[ItemCreateViewController alloc] initWithNibName:#"ItemCreateViewController" bundle:nil]; //Ensure you use correct nib file name
Then you can push it
[[self navigationController] pushViewController:itemCreateViewConrtoller animated:YES];
But since you are using a Storyboard you could have just added the new view to your storyboard, linked the button to the new view with a "push" segue and everything would have been done for you.
I have a UITabBarController bassed app. I'm instantiating it from the app delegate and adding a custom button in the tab bar controller. when that button is clicked, I want to present another view modally, but I cant seem to figure out how to do it. to add the button I'm basically doing this
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
[self.tabBarController.view addSubview:button];
[button addTarget:self action:#selector(showModalViewController:) forControlEvents:UIControlEventTouchUpInside];
and also in the app delegate I have a method
- (void) showModalViewController {
DummyViewController *addController = [[DummyViewController alloc]
initWithNibName:#"DummyViewController" bundle:nil];
//addController.delegate = self;
self.tabBarController.selectedViewController = [self.tabBarController.viewControllers objectAtIndex:0];
// Create the navigation controller and present it modally.
[self.tabBarController.selectedViewController presentModalViewController:addController animated:YES];
// The navigation controller is now owned by the current view controller
}
I keep getting unrecognized seletor
Your selector looks for a method named showModalViewController: but your actual method is named showModalViewController. Change one or the other.
Either change the selector to #selector(showModalViewController) to match the existing method, or change the method to:
- (void)showModalViewController:(UIButton *)button {
Don't change both.
my iOS application use storyboard
it have 2 view controllers:
- main storyboard view controller
- and popover view controller with some objects in it
i've got a button on main view controller and it creates programing every time i run the application:
*CGRect buttonFrame = CGRectMake(10., 10., 120., 50.);
oneButton = [UIButton buttonWithType:UIButtonTypeCustom];
[oneButton setImage:[UIImage imageNamed:[NSString stringWithFormat:#"someImage.png", img]] forState:UIControlStateNormal];
[oneButton setTag:img];
[oneButton setFrame:buttonFrame];
[oneButton addTarget:self action:#selector(pressButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:oneButton];*
the action of this button show my popover view like that:
*- (void) pressButton:(id)sender {
popoverViewController *popoverFrame = [self.storyboard instantiateViewControllerWithIdentifier:#"myPopoverView"];
popoverWithObjects = [[UIPopoverController alloc] initWithContentViewController:popoverFrame];
[popoverWithObjects presentPopoverFromRect:[sender frame] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}*
from now the situation is, that i can't send to my button any message or result.
i want to say to my program button (note - i've got only sender of this button action:#selector(pressButton:) ) that popover return some result or some action of an object in popover send anything (string for example)
Or in another words when i interact with any object like button on popover view, i want to change parent buttons title label
Use NSNotificationCenter.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsnotificationcenter_Class/Reference/Reference.html
You need to use delegate. Check out my answer to this similar question from this SO.
Edit: Link to tutorial on Storyboard and delegate pattern usage. And my original answer to delegate on this SO
How about writing a function on your Main View Controller to do what you want. Then call that function from the popover? (i.e. use 'prepare for segue' to send the popover the id of the Main View Controller, and then use that id to call the function on Main View from the popover)