My app takes a picture and then needs to ask the user what they want to name their image.
I've tried using UIAlertView, but I'm having this issue where the string itself isn't collected on time because the code doesn't pause to wait for user input.
What I want to try now is to make a new view controller that just has a text field for the user to input their PDF file's name. What I want to know is how exactly it would conceptually work (or if it would work at all).
Pseudo code:
1. The user clicks on a take picture button, which runs a method that captures the image.
2. After the image is in memory, the View Controller that has the text field is presented.
3. The user inputs the information into the text field
4. The view is dismissed
5. The code in the method that captures the image continues.
Is this how view controllers work? If I present a view controller in from a button, and then dismiss the presented view controller from another button in the new view controller, does the code in the original method continue executing where it left off?
No. The code in the original method executes immediately after presenting the UIAlertView (or UIActionSheet, or any other thing). It doesn't wait for the dismissal.
To handle event of dismissing the UIAlertView, for example there is mechanism called Delegation. It works like this:
You create the UIAlertView in View Controller.
You set UIAlertView's .delegate property to some object. Typically the View Controller itself. That object should implement several methods, that UIAlertViewDelegate defines.
You present the Alert and your code continues immediately.
When user click on some button, the UIAlertView calls one of the defined methods on its Delegate. In our case, the View Controller, which brings us back to our code.
In your delegate method, you “continue” what you wanted to do before Alert was presented.
Example code:
- (void)presentAlert {
// 1
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hello!"
message:nil
delegate:self // 2
cancelButtonTitle:#"Eh?"
otherButtonTitles:#"Hello!", #"Hi!", #"Aloha!" nil];
[alert show]; // 3
NSLog(#"Did present alert"); // immediately executed
}
// Between these method calls may pass several seconds, minutes or eternity.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// 4, 5
NSLog(#"Did click button %#", [alertView buttonTitleAtIndex:buttonIndex]);
}
In addition, you will have to declare UIAlertViewDelegate protocol on your View Controller:
#interface YourViewController : UIViewController <UIAlertViewDelegate>
I believe you are misunderstanding Viewcontrollers quite a bit. But i cannot explain all the issues here, or this post would turn into a book. So heres a very helpful link (that i hope you already did read) and a few hints:
https://developer.apple.com/library/ios/featuredarticles/viewcontrollerpgforiphoneos/Introduction/Introduction.html
Viewcontrollers are presented either modally (stacked on top of each other) or in a common ParentViewController (such as a NavigationController or a SlideViewController). The presenting controller will always either keep the controller running in the background or finish the Viewcontrollers logic before presenting a new one. So the shortest possible answr to your question is no.
Pay special attention to the Viewcontroller lifecycle. ViewControllers have special callback methods that get invoked under certain circumstances. Look out for the viewDidLoad and viewDidAppear - methods, this will clear your confusion quite a bit.
Last, not least, look into the delegate pattern. Delegates are a way of sending messages between viewcontrollers. So you could make the ViewController that takes the name send a message with the chosen name to the viewController that took the picture.
Heres a link to the delegate programming guide:
https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
i hope that gets you started
Related
I have a a VC structure like this
UIPageViewController -> detailViewController -> popoverviewcontroller
The popoverviewcontroller is dismissed using an unwind segue, bringing us back to the detailviewcontroller
Now, after the popover is done being dismissed, I would like to refresh the pages on the pagecontroller, since the action the user takes has changed the data.
I would also like to display an alert notifying the user about whether they were successful.
So I tried putting this code in the pageViewcontroller
- (IBAction) unwindFromPopup:(UIStoryboardSegue*)unwindSegue{
[self refreshPages];
UIAlertController * alertController = [UIAlertController alertControllerWithTitle:#"alert" message:#"this should appear" preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alertController animated:YES completion:nil];
}
I tried moving the above code to the detail view controller instead, but I am getting no result from this. No error or anything, just a complete lack of alert. I put a breakpoint in the segue, and the code gets called. But no alert.
I thought of putting the code in one of the respective viewDidAppear methods, but for some reason viewDidAppear does not get called for either the pageviewcontroller or the detailview controller after I dismiss the popup.
So at this point I have no idea how to make this alert appear.
Do I need to post my full code, or is my problem apparent with the details I've included?
Thanks - based on your comment ... long ago in a distant version of iOS I performed all the possible segues and noted what gets called when and have a table of that that I based my answer on. I must admit, nowadays I get most done using the presentation controller delegate.
Anyhow, to reply to your question, when you pop or modally present a controller, the controller that is being presented will message beingPresented and beingDismissed when it is done and you might be able to use this for what you are trying to do.
When you push a controller it will message isMovingToParentViewController when shown and isMovingFromParentViewController when dismissed, again in the controller being presented.
Back to a pop ... it will message prepareForSegue in the presenting VC and viewWillAppear and viewDidAppear in the presented VC and, when dismissing, will message only viewWillDisappear and viewDidDisappear in the presented VC, thus your problem. At least it will also message beingDismissed as mentioned and if you can use that I am really glad for you.
I need a way to change text depending of user input, when user tap back button. I followed that solution: Find out if user pressed the back button in uinavigationcontroller?
and did add following code in viewDidLoad:
if ([self isMovingFromParentViewController]) {
NSLog(#"isMoved");
[self.delegate stringChangedTo:self.myTextField.text atIndex:self.indexToPass];
}
However, nothing changed. More to say, method is not called (NSLog dont output a string).
How could i find a way to call delegate when user tap back button?
That code needs to be in viewWillDisappear: or viewDidDisappear:. not viewDidLoad.
viewDidLoad is called when the view controller's view is loaded. You want to call the delegate when the view controller is being dismissed.
There is also a UINavigationControllerDelegate protocol. You can get notified when a given view controller is shown by implementing either of these:
-navigationController:willShowViewController:animated:
-navigationController:didShowViewController:animated:
ADDENDUM:
In my opinion, using the delegate is a cleaner design, because you get notified precisely when a navigation event occurs. View controller life cycle methods such as -viewDidDisappear:, etc. can get called when you present/dismiss a modal view controller, and require that you add logic to discern those.
I have an iOS app running right now in a storyboard with 3 viewcontrollers. The first one (initial view) features a play button to start a music stream and image for album cover of currently playing song. This scene has a a navigation controller and a bar button on it that will lead the user to the next view...
A list view populated with hard coded stream's that the user can choose from. Very simple and working fine still.
After choosing one, the user goes to a preview page that tells them about the stream before it begins to play. Still working like a charm until they want to continue from here.
If they user selects the stream from the preview page, the app "should" return the user to the initial ViewController and swap out the playing stream for the one selected. At first, I was mistakenly creating a new instance of the initial viewController, and after fixing that mistake, now have a few more questions someone might be able to help me with.
here is the IBAction for the button to select the stream:
- (IBAction)returnHome:(id)sender
{
[[self navigationController] popToRootViewControllerAnimated:YES]; // goes back to first view on the stack
}
Before finding this logic, I was using the prepareForSegue and setting the stream value of the destination to be what was selected. I was also trying to save the state of the first view but was unsure how to pass it down the line (or retain it) since I am moving through 3 ViewControllers and using a modal segue so they can go back if they choose to not pick a new stream.
Any advice will help, but please refrain from simply posting a link to the references. I have been there for 3 days straight and they do not speak a very beginner friendly lingo in the iOS reference docs.
You want to be able to inform Your first VC that user changed track?
You can simply use NSNotificationCenter.
First You have to "tune in", i.e. in viewDidLoad, Your view controller to listen to particular notifications:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changeStream:) name:#"UserWantToChangeStream" object:nil];
}
and implement method:
- (void)changeStream:(NSNotification *)notification
{
NSString *newStreamName = notification.object;
/* Change the stream code */
}
Don't forget to stop listening to the notifications:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Then You post notification after user has performed an action:
[[NSNotificationCenter defaultCenter] postNotificationName:#"UserWantToChangeStream" object:#"new_stream_name"];
In the example I pass NSString new_stream_name but You can pass any object.
Your first view controller will be informed.
Have you considered the usage of unwinding segues? they are a little bit tricky to understand and use, but they surely can help you out.
First of all, you have to create an IBAction on your FIRST viewcontroller (== the view controller where you want to "land" and pass your list selection to) that takes a UIStoryboardSegue as single parameter and leave the implementation empty. For example
- (IBAction) returnToHome:(UIStoryboardSegue*) segue{;}
Then, in your preview page view controller (in storyboard), drag a segue from your button (the one which is triggerng the IBAction that pops the navigation controller) to the little exit symbol in the lower right side of the view controller. A menu should pop out asking for returnToHome method. Delete from the button the previous IBAction as well (the one you called -(IBAction)returnHome:(id)sender)
In this way you should be able to do the same thing as before (popping back to the root view controller) without solving the problem BUT! if you implement an override of prepareForSegue:sender in your last view controller you'll have a reference to the root view controller where you can do whatever you need.
- (void) prepareForSegue:(UIStoryboardSegue*) segue sender:(id)sender
{
ViewController *vc = (ViewController *)segue.destinationViewController;
[vc.audioplayer pause];
vc.stream = self.stream;
}
This is a cleaner way to achieve the same result, since you're not making assumpions on your viewcontrollers hierarchy (what if tomorrow you'll add another view controller BEFORE the first one? The app will surely crash).
By the way, if you're into Storyboard/Segue business, check out my library which really simplifies storyboard work when it comes to "passing parameters": https://github.com/stefanomondino/SMQuickSegue (you can install it via cocoapods with pod 'SMQuickSegue')
I have figured out at least how to get it to work. I had a second navigation controller in the storyboard and after deleting that it successfully went back 2 views instead of just one.
Here is my logic for retaining the stream data and bringing it back to the initial view controller if anyone is curious.
- (IBAction)returnHome:(id)sender
{
ViewController *vc = (ViewController *)[self.navigationController.viewControllers objectAtIndex:0];
[vc.audioplayer pause];
vc.stream = self.stream;
[self.navigationController popToRootViewControllerAnimated:YES];
}
since I now only have one Navigation Controller, the index for the initial will be 0. I can grab the initial view and put a reference to it in vc.
Then I simply access the audioplayer from it, and if it is still playing, stop it and load up the scene.
Then in the initial ViewController logic, I have the viewDidAppear method to load the new stream selection into the display labels and images.
- (void)viewDidAppear:(BOOL)animated
{
// If there is no stream selected (first run)
// set the stream to RAPstation default
if (self.stream == nil)
{
self.stream = [[Stream alloc]initWithName:#"RAPstation" ...];
}
self.snameLabel.text = self.stream.sName;
self.sdescLabel.text = self.stream.sDesc;
}
Not sure if this is the right but it definitely works for now. If anyone can help make it cleaner please feel free.
I'm having trouble piecing this all together. I have a view controller that opens up another (pushes it on to the navigation stack). On that presented view controller, the user enters a value in a text view. When the user pushes the back button in the navigation, I want to be able to pass the value that they entered in the text view back to the presenting controller.
I've looked for a way to use unwind segue with the back button but haven't found anything. When I create my back button (programmatically) I use initWithTitle:style:target:action but I'm not sure how in implementing the action method that I'll be able to access the value set in the presented controller. Might have to use a delegate to link the two, but not sure of the exact integration point for this scenario.
I feel like I'm so close here and a little help would get me there. Thanks!
The two most common models to use for this interaction are for the child view controller to have either a delegate or a completion block. Either would be set in the prepareForSegue method. My personal preference is the completion block method just because it keeps code contained, but ymmv.
There are also multiple models for detecting when your child view controller is dismissed and you need to invoke the delegate and/or completion:
Use a custom back button. Not a fan of this as it can be an issue to create a back button that really looks and acts like the Apple original, especially if supporting iOS 6 and iOS 7.
Hook viewDidDisappear and see if you're still in the navigation controller's viewControllers array. This is better as the back button works right, but it still feels kind of hokey.
Use the UINavigationBarDelegate method navigationBar:shouldPopItem: This is attractive, especially if you have other validation that needs to happen like checking for saved/unsaved values. To implement this you'll have to subclass UINavigationController and forward the method to your child view controller.
EDIT: Details on Option 2:
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(![self.navigationController.viewControllers containsObject:self])
{
// We're not still in the navigation stack so we must've been
// popped. If we were pushed, viewDidDisappear would be called
// but viewControllers containsObject:self would be true
}
}
EDIT: Clarified Option 3: in your navigation controller subclass
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController* top = self.topViewController;
if([top respondsToSelector:#selector(navigationBar:shouldPopItem:)])
return [(id)top navigationBar:navigationBar shouldPopItem:item];
return [super navigationBar:navigationBar shouldPopItem:item];
}
Then you can implement navigationBar:shouldPopItem: in the classes that need the functionality.
the back button does not actually comes up with any event associated with itself so that you can pass the values between the previous and to be Popped ViewController.
You would have to implement Delegate pattern to pass values. In this case as you cant catch when backButton is pressed, you need to use custom leftBarButtonItem or use a image with < in itself.
The problem is only happening with iOS4.3. I'm using ARC and my Base SDK is iOS6.
In -viewDidAppear of my view controller, I check if this is the first time the app has been started and if so, then I create and show a UIAlertView. I assign that UIAlertView to a strong property on the view controller and set self as the UIAlertView delegate.
self.uiAlertView = [[UIAlertView alloc] initWithTitle:#"Welcome!"
message:messageString
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"View Tutorial Videos", #"Email Support", nil];
When I tap one of the buttons, the app crashes complaining that -alertView:didDismissWithButtonIndex: was sent to a deallocated instance. The delegate is the view controller that is displaying the UIAlertView.
On all subsequent launches of the app, when the UIAlertView isn't shown there are no problems. The view controller is definitely not being deallocated.
If I display the UIAlertView but set the delegate to nil, then there is no problem and the app continues working, so clearly the view controller hasn't been deallocated because I can keep using it.
What is happening? This only causes a problem with iOS4.3.
EDIT: Based on suggestions in the comments, I added some more log messages in different places.
I've discovered that the view controller IS getting dealloc'd, but only if that view controller displays the UIAlertView. What in the world would cause the view controller to get dealloc'd just because it sets itself as the delegate of a UIAlertView and then displays it?
My app delegate has a strong reference to the view controller, so there is absolutely no reason that I can see for the view controller to get dealloc'd.
EDIT 2: I've discovered that during start up my main view controller is being instantiated TWICE. The first one is the one creating the UIAlertView and that one is getting dealloc'd. The second one is the one that I've been able to interact with afterwards that made me think the view controller was still there and operable.
However, I can't figure out where or why my view controller would be created twice. I don't have any alloc/init statements for the view controller. It only exists in the MainWindow_iPhone.xib.
The first time viewDidLoad is called on my view controller, the stack frame above is [UIViewController view]. The second time viewDidLoad is called on the second instance of my view controller, the stack frame above is [UINib instantiateWithOwner:options:]
EDIT 3: I've "fixed" the problem, but I don't understand why this would happen. Perhaps you can help me understand.
In my MainWindow_iPhone.xib, I created my root view controller and assigned it to an IBOutlet on my app delegate. Instead, I deleted the view controller from the xib and created it in code in the -application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions ... and the problem disappeared.
Why in the world would the view controller be created twice when in the xib?
I have this issue before. alertView:didDismissWithButtonIndex: is called after alertView: clickedButtonAtIndex:. You most likely deallocate the view controller in alertView:clickedButtonAtIndex by doing something like [self.navigationController popViewControllerAnimated:YES].
UIAlertView delegate is assign not weak reference. When delegate is deallocated, it is not set to nil automatically. That's reason why your code is crashed.
i fixed this issue commenting this method (or deleting it).
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSLog(#"se apreto cancel");
}
as James Wang said, the didDismissWithButtonIndex is called after clickedButtonAtIndex so i commented it to avoid the crash.