Passing an Action to a View via DismissViewController - ios

ViewController
UIWebView: webView - a simply UIWebView
UIButton: aboutButton - takes you to the AboutViewController
AboutViewController
UIButton: websiteButton - connected to clickWebsiteButton
IBAction: clickWebsiteButton - dismiss AboutViewController, load http://websiteURL.com/ in webView (which is within the ViewController)
AboutViewController Code
// AboutViewController.h
#import "ViewController.h"
#class ViewController;
#interface AboutViewController : UITableViewController <UIWebViewDelegate> {
ViewController *viewController;
}
// AboutViewController.m
-(IBAction)clickWebsiteButton:(id)sender {
[viewController.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://websiteURL.com/"]]];
[self dismissModalViewControllerAnimated:YES];
}
Question
I want to be able to load http://websiteURL.com/ within the UIWebView upon dismissing the view via the IBAction. As of now, all it does is dismiss the view but not load the URL within the WebView. The WebView is working and loading URL's properly, I'm just having troubles loading this URL from a different view. Any ideas?
Thanks

I answered your other question regarding persistent data storage. That is a different way of getting your viewControllers to share data so you may no longer need this, but just in case...
The issue is that you are calling a method on your presenting viewController before you have dismissed the presented viewController (aboutViewController). It needs to be invoked after the dismiss process is complete.
This method:
dismissModalViewControllerAnimated:
Is deprecated in iOS6, and since iOS5 you are encouraged to use this instead
dismissViewControllerAnimated:completion:
where completion takes a block argument. Code you place in the completion block will execute after the dismissing is complete. You can send a message to the presenting viewController here.
self.presentingViewController is a reference to the viewController which presented aboutViewController - it is provided by iOS as part of the presenting process. But you can't use it in the completion block as it gets nulled during the dismiss process, so you need to copy it to a local variable first.
In aboutViewController...
-(IBAction)clickWebsiteButton:(id)sender
{
//to use self.presentingViewController in the completion block
//you must first copy it to a local variable
//as it is cleared by the dismissing process
UIViewController* presentingVC = self.presentingViewController;
[self.presentingViewController dismissViewControllerAnimated:YES
completion:
^{
if ([presentingVC respondsToSelector:#selector(loadRequestWithString:)]) {
[presentingVC performSelector:#selector(loadRequestWithString:)
withObject:#"http://websiteURL.com/"];
}
}];
}
In your presenting viewController, make a method to accept the string argument:
- (void) loadRequestWithString:(NSString*)webString
{
NSURL* requestURL = [NSURL URLWithString:webString];
[self.webView loadRequest:[NSURLRequest requestWithURL:requestURL]];
}

One option is to use a delegate callback. With your current code, the viewController instant is nil. I have an example how to implement a delegate pattern here.

Remember that if you are using UINavigationController you'll have to do
UINavigationController *viewConNav = (UINavigationController *)self.presentingViewController;
YourVC *viewCon = (YourVC *)viewConNav.topViewController;

Related

Attempt to present <UIImagePickerController on UIViewController> whose view is not in the window hierarchy [duplicate]

I have found that, when compiling for iOS 8 (and running in iOS 8), a UIWebView cannot show the camera/image picker if the UIWebView is in a view controller presented modally. It works without problem in view controllers directly “hanging” from the window rootViewController or view controllers pushed from it.
The test application can be found at https://dl.dropboxusercontent.com/u/6214425/TestModalWebCamera.zip but I will describe it below.
My test application (built with storyboards, but the real application doesn’t use them) has two view controllers (unoriginally named ViewController and ViewController2). ViewController is contained in a UINavigationController which is the root view controller. ViewController contains a UIWebView (works OK), a button that “shows” (“pushes”) ViewController2, and a UIBarButtonItem which modally presents ViewController2. ViewController2 has another UIWebView which works when “pushed” but not when “presented”.
Both ViewController and ViewController2 are loaded with:
- (void)viewDidLoad {
[super viewDidLoad];
[self.webView loadHTMLString:#"<input type=\"file\" accept=\"image/*;capture=camera\">" baseURL:nil];
}
When trying to use the modal UIWebView Xcode prints the following in the console and dismisses the app modal:
Warning: Attempt to present <UIImagePickerController: 0x150ab800> on <ViewController2: 0x14623580> whose view is not in the window hierarchy!
My current theory is that the changes in UIActionSheet to UIAlertController might have produced this situation, but it’s quite hard to prove. I will open a Radar with Apple, just in case.
Has someone found the same situation and some workaround?
I found that in iOS 8.0.2 iPad does not seem to have that bug but iPhone still does.
However, overriding following in the view controller containing the uiwebview
-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
And checking that there is a presentedViewController seems to work.
But need to check side effects
#import "UiWebViewVC.h"
#interface UiWebViewVC ()
#property (weak, nonatomic) IBOutlet UIWebView *uiwebview;
#end
#implementation UiWebViewVC
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:#"http://html5demos.com/file-api-simple"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.uiwebview.scalesPageToFit = YES;
[self.uiwebview loadRequest:request];
}
-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
{
if ( self.presentedViewController)
{
[super dismissViewControllerAnimated:flag completion:completion];
}
}
#end
I have the same issue on iOS 9. Try to add ivar '_flag' and then override this methods in view controller with UIWebView
#pragma mark - Avoiding iOS bug
- (UIViewController *)presentingViewController {
// Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller
if (_flagged) {
return nil;
} else {
return [super presentingViewController];
}
}
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
// Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller
if ([viewControllerToPresent isKindOfClass:[UIDocumentMenuViewController class]]
||[viewControllerToPresent isKindOfClass:[UIImagePickerController class]]) {
_flagged = YES;
}
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)trueDismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
// Avoiding iOS bug. UIWebView with file input doesn't work in modal view controller
_flagged = NO;
[self dismissViewControllerAnimated:flag completion:completion];
}
This works for me fine
For me I tend to have a custom UINavigationController so that multiple views can share the same logic. So for my workaround (in Swift) here is what I put in my custom NavigationController.
import UIKit
class NavigationController: UINavigationController {
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
if let vc = self.presentedViewController {
// don't bother dismissing if the view controller being presented is a doc/image picker
if !vc.isKindOfClass(UIDocumentMenuViewController) || !vc.isKindOfClass(UIImagePickerController) {
super.dismissViewControllerAnimated(flag, completion:completion)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// make sure that the navigation controller can't be dismissed
self.view.window?.rootViewController = self
}
}
This means that you can have loads of view controllers with webviews and they'll all work with the file upload element.
I was having a similar issue, and I have discovered that the UIWebView elements in IOS do not support the html element:
I am not sure why Apple chose to not support this IMPORTANT html element, but I am sure they have their reasons. (Even though this element works perfectly on Safari on IOS.)
In many cases, when the user clicks this kind of button in a UIWebView, it will let them take/ choose a photo. HOWEVER, the UIWebView in IOS does not have the capability to attach files like this into the POST data when the form is submitted.
The Solution: To accomplish the same task you can create a similar form in InterfaceBuilder with a button that triggers the UIImagePickerController. Then, you create you an HTTP POST request with all of the form data and the image. It isn't as hard as it sounds, check out the link below for some sample code that gets the job done: ios Upload Image and Text using HTTP POST
Alright, here's the workaround I ended up using. It's a 2 minute change, pretty hacky, but works as expected and avoids the bug we're all having. Basically, rather than presenting the child view controller modally, I set it to the window's rootViewController and keep a reference to the parent controller. So where I used to have this (in parent view controller):
presentViewController(newController, animated: true, completion: nil)
I now have this
view.window?.rootViewController = newController
In both cases, the parent controller is newController's delegate, which is how I keep the reference.
newController.delegate = self
Finally, where in my delegate callback for closing the modal, where I used to have this:
dismissViewControllerAnimated(true, completion: nil)
I now have this:
viewController.view.window?.rootViewController = self
So we get the same effect of a view controller taking over the screen entirely, then yielding its control when it's done. In this case though, it's not animated. I have a feeling animating the controller transition wouldn't be very hard with some of the built in view animations.
Hopefully you're already using the delegate pattern to manage communication with the modal controller, per Apple's recommendation, but if you're not I'm sure you can use any number of methods that keep a reference and get called back.
My solution is to create custom View Controller with custom modal presentation based on controllers child-parent hierarchy. Animation will be the same so the user will not notice the difference.
My suggestions for animation:
animateWithDuration:(animated ? 0.6 : 0.0)
delay:0.0
usingSpringWithDamping:1.f
initialSpringVelocity:0.6f
options:UIViewAnimationOptionCurveEaseOut
It will look exactly like default modal presentation animation in iOS7/8.
What I did what every time the view controller is change, convert the destination view in root.
from first view controller:
let web = self.storyboard?.instantiateViewController(withIdentifier:"web") as! UINavigationController
view.window?.rootViewController = web
this is how I pass the root to the other, and when comeback to the first one I made this.
from web view controller:
let first = self.storyboard?.instantiateViewController(withIdentifier: "reveal") as! SWRevealViewController
present(first, animated: true, completion: nil)
and everything is fine, I can show the modal for select photo from library, take photo and everything else.
I don't know if that is a good practice but works fine in emulator and device.
Hope it helps.

How to have an action in one view controller start another in a different view controller?

New to iOS development & Objective-C and am a little unsure how to go about solving this issue.
I'm working on a project that works like a video player. There are two ViewControllers:
MenuViewController (has a list of titles that act as buttons)
PlayerViewController (view where video plays)
In the MenuViewController, I want to be able to click onto a button (video title) :
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
[self presentModalViewController:vc animated:YES];
}
and have an action that's currently defined in the PlayerViewController automatically execute as soon as its loaded.
Is there a way to have a button in one ViewController call the action in another ViewController as soon as that second ViewController has loaded?
Thanks!
The right way to solve this problem will be set up a delegate pattern between the two view-controllers.
Example:
#protocol PlayerViewControllerDelegate<NSObject>
{
-(void)playerViewControllerViewDidLoad:(PlayerViewController *)playerVC;
}
Then, in PlayerViewController.h create a weak delegate variable:
#property (nonatomic, weak) id<PlayerViewControllerDelegate> delegate;
In PlayerViewController.m, notify the delegate on viewDidLoad:
-(void)viewDidLoad {
[super viewDidLoad];
[self.delegate playerViewControllerViewDidLoad:self]
}
In MenuViewController:
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
vc.delegate = self
[self presentViewController:vc animated:YES];
}
Finally, implement the protocol in MenuViewController, and you're ready to go:
#interface MenuViewController : UIViewController<PlayerViewControllerDelegate>
#end
#implementation MenuViewController
-(void)playerViewControllerViewDidLoad:(PlayerViewController*)playerVC
{
[playerVC playVideo];
}
#end
It is usually not good practice to have one controller controlling the behavior of another. Instead, you can have the MenuViewController create the PlayerViewController and set variables so the new player knows how to behave based on its internal state.
There are several UIViewController methods that you can override in order to perform actions during the controller's lifecycle. Based on your question it seems like you want the viewDidLoad method.
I am not sure how you are passing videos between controllers, but if you were using URLs (to Youtube videos for example) then you could do something like the following:
// MenuViewController.m
- (IBAction)videoOne:(id)sender {
PlayerViewController* vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
// Pass any necessary data to the controller before displaying it
vc.videoURL = [self getURLForSender:sender];
[self presentViewController:vc animated:YES completion:nil];
}
// PlayerViewController.m
- (void)viewDidAppear {
// View did appear will only be called after the controller has displayed
// its primary view as well as any views defined in your storyboard or
// xib. You can safely assume that your views are visible at this point.
[super viewDidAppear];
if (self.videoURL) {
[self playVideo];
}
}
You would need to define the property videoURL on PlayerViewController and expose it publicly. If you are using local files (such as from the user's photo storage) you could pass the video to the new view controller before presenting it.
There are other UIViewController lifecycle methods that you can override. They are explained in more depth in this post as well as Apple's UIViewController Documentation.
Edit: changed presentModalViewController:animated: to presentViewController:animated:completion: and changed viewDidLoad to viewDidAppear as it seems more appropriate for the question.
presentModalViewController:animated: is deprecated. Use presentViewController:animated:completion: instead.
In the completion Block, you can call a method on the presented view controller:
[self presentViewController:otherVC
animated:YES
completion:^{ [otherVC startPlaying]; }];
The completion is run after the presented controller's viewDidAppear.

Presenting two UIViewControllers, one after another

I am new to iOS development and I am doing little project as a research.
I have an app where after I start it I am showing MainViewController, but if this is the first launch of this app I want to show Sub1ViewController (names are made up) using presentViewController method called in MainViewController.
After user puts in some data on Sub1ViewController I invoke dismissViewController method to hide it.
The hard part starts here - I have no idea how to capture the event when Sub1ViewController is dismissed and I can present Sub2ViewController also using presentViewController invoked from MainViewController. All the time I am getting messages that I am trying to present view when another present or dismiss is in progress.
PS: I am using Xamarin, but I also understand objective-c.
hi you can try out this
[self dismissViewControllerAnimated:YES completion:^{
//code to be executed with the dismissal is completed
// for example, presenting a vc or performing a segue
}];
you can write code after completion of dismiss one view controller
or
You can achieve this by present view after some delay in dismiss method
-(void)onDismisViewController{
[self performSelector:#selector(presentSecoundViecontoller) withObject:self afterDelay:1];
}
-(void)presentSecoundViecontoller
{
SecoundViewController *secoundVC = [[SecoundViewController alloc] initWithNibName:#"secoundVC" bundle:nil];
[self.navigationController presentViewController:secoundVC animated:YES completion:NULL];
}
EDIT
Try this i Try and it work
crate one delegate Method in subviewcontroller1 and set delegate in mainviewcontroller and implement method in mainvie and present subviewcontroller2 in this delegate method its work
try this if not let me know will post code.
Delegate Creation on subviewcontroller1.h file
#protocol st1Protocol <NSObject>
- (void)presentViewController;
#end
#interface SubViewController1 : UIViewController
#property (nonatomic,assign) id <st1Protocol> delegate;
- (IBAction)dissmiss:(id)sender;
SubViewcontroller1.m file
i put dismiss view on button click of subviewcontroller1 you do this in you dismiss method
- (IBAction)dissmiss:(id)sender {
[self dismissViewControllerAnimated:YES completion:^{
//code to be executed with the dismissal is completed
// for example, presenting a vc or performing a segue
[self.delegate presentViewController];
}];
}
now implementation of this delegate method in main view controller.
implement delegate method in .h file
#interface StViewController : UIViewController<st1Protocol>
set delegate in mainviewcontroller.m file view didload
- (void)viewDidLoad
{
[super viewDidLoad];
subViewcontroller1 *st1=[[subViewcontroller1 alloc]initWithNibName:#"subViewcontroller1" bundle:nil];
st1.delegate=self;
[self presentViewController:st1 animated:YES completion:nil];
// Do any additional setup after loading the view from its nib.
}
delegate method implementation in main view controller
-(void)presentViewController{
SubmViewcontroller2 *st2=[[SubmViewcontroller2 alloc]initWithNibName:#"St2ViewController" bundle:nil];
[self presentViewController:st2 animated:YES completion:nil];
}
hope this may help you happy coding.

UIPopoverController: update view after it is dismissed

On iPad simulator, I have a ViewController A that presents an UIPopoverController whose contentViewController is ViewController B, inside which I have a button to dismiss the UIPopoverController.
When it is dismissed, I need to update the view of ViewController A based on some field in ViewController B.
In order to do this, I am declaring ViewController A as a property (weakref) of ViewController B so that within ViewController B where it dismisses the popover, I can say:
[self.viewControllerA.popover dismissPopoverAnimated:YES];
self.viewControllerA.popover = nil;
self.viewControllerA.textLabel.text = self.someField
Is this the correct way of doing it? Since there is no callback when we dismiss the popover pragmatically, I can't think of any better solution.
Anybody has a better idea? Passing view controllers around just seems awkward to me.
The best way is use of Delegation, just declare the delegate in your controller B like
#protocol ControllerSDelegate <NSObject>
-(void) hidePopoverDelegateMethod;
#end
and call this on action for passing the data and dismiss of controller like
if (_delegate != nil) {
[_delegate hidePopoverDelegateMethod];
}
and
in your controller A you can handle this delegate call
-(void) hidePopoverDelegateMethod {
[self.paymentPopover dismissPopoverAnimated:YES];
if (self.paymentPopover) {
self.paymentPopover = nil;
}
[self initializeData];
}
I think, delegates or sending NSNotification will make better.
Note:
A change of the execution sequence will do more perfection to your current code.
self.viewControllerA.textLabel.text = self.someField
[self.viewControllerA.popover dismissPopoverAnimated:YES];
self.viewControllerA.popover = nil;

viewWillAppear method not being called after popToRootViewController

I have a view controller,that is a UINavigationController, that at some point pushes (navigationcontroller pushViewController: animated:) a second view that later pushes a third view where I have a button that pops back to the root view (popToRootViewController: animated:). The problem is that after the view is poped back to the root one, the method viewWillApper of the root view is not being called. I've set some breakpoints to check it and it's just not passing through it. I have a method to reload some contents of my root view placed in the viewWillApper and its being completely passed by after the popToRootViewController: animated.
Any idea of what's going on?
Thanks
I used a delegate method to force the update of my view after popToRootViewController. My rootViewController called a network upload class and, on completion, I wanted to reset the form fields on the rootViewController.
In the network upload class, I created a delegate protocol:
#protocol MyNetworkDelegate <NSObject>
#required
- (void) uploadCompleted;
#end
#interface MyNetworkUploader : NSObject{
id <MyNetworkDelegate> _delegate;
}
#property (nonatomic,strong) id delegate;
//other properties here
+(id)sharedManager;
-(int)writeAssessments;
#end
In MyNetworkUploader.m:
-(int)writeAssessments{
//code here to do the actual upload
//.....
//this is a non-view class so I use a global navigation controller
//maybe not the best form but it works for me and I get the required
//behaviour
[globalNav popToRootViewControllerAnimated:NO];
[[globalNav.view viewWithTag:1] removeFromSuperview];
[_delegate uploadCompleted];
}
Then, in my rootViewController:
//my upload is done within a completion block so I know when
//it's finished
typedef void(^myCompletion)(BOOL);
-(void) uploadAssessment:(myCompletion) compblock{
//do the upload
sharedManager=[MyNetwork sharedManager]; //create my instance
sharedManager.delegate=self; //set my rootViewController as the network class delegate
int numWritten= [sharedManager writeAssessments];
compblock(YES);
}
#pragma mark - protocol delegate
-(void)uploadCompleted{
//this is a local method that clears the form
[self clearTapped:nil];
}
I'm NOT proposing that this is the best solution but it worked a treat for me!
When using a navController to push, assuming VC1 is pushing VC2 and you are using a custom presentation style to push your VC2, depending on which style you choose viewWillAppear of VC1 will not be called when VC2 is popped. Here is a list of when is called according to its presentation style.
UIModalPresentationStyle, iPhone, iPad
.fullScreen YES YES
.pageSheet YES NO
.formSheet YES NO
.currentContext YES YES
.custom NO NO
.overFullScreen NO NO
.overCurrentContext NO NO
.blurOverFullScreen  only on tvOS - N/A, N/A
.popover YES NO
.none CRASH CRASH
reference found here

Resources