Presenting a modal view manually from an actionsheet - ios

I'm pretty new to iOS dev, and I've been reading a ton on this problem, but still can't figure it out.
I have a button in an action sheet that is supposed to activate and present a slide-up modal that is a from/to date picking screen (it has its own controller DatePickerViewController. The action sheet is triggered by a button in the toolbar of a NavigationViewController's subview ("Map of Shows" view, top left button). Graphic shows the current storyboard relationships:
The code for this sequence looks like this:
// ShowsContainerController.m
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if( buttonIndex == 1 ){
// 1. Activates from actionsheet
[(NavigationViewController *)self.parentViewController showDateSelect];
}
}
// NavigationViewController.m
// 2. fires up the datepicker view
-(void)showDateSelect
{
pickerView = [[DatePickerViewController alloc] init ];
[self presentViewController:pickerView animated:YES completion:nil];
}
// DatePickerViewController.m
// 3. Instantiation of this controller. Definitely fires nslog.
-(void)viewDidLoad
{
NSLog(#"Here");
}
Once "here", the screen just goes black. I assume this is because I'm not doing something right either with the instantiation of the date picker controller, or with the segue to it. All of the views in question are associated with their respective controllers in the storyboard configs. To further confuse the issue, I have a UITableViewController that I created for another screen, and just for shits and giggles, attempted to load that, and it worked fine. I then created another completely separate UIViewController, pointed it back to the controller files that control the current non-working one, and it bombs as well, so I'm thinking the issue are the header and main files for the non-working UIViewController. ed. scratch that last note; I created a completely new header, main file and NIB for the view and it still didn't work. I don't know what the hell the deal is.
Any and all help would be greatly appreciated.
Addendum
- (IBAction)showOptions:(id)sender
{
NSString *showTypes;
if( self.onlyShowPreferredEvents ){
showTypes = #"\u2713 Only shows that I'll like";
} else {
showTypes = #"Only shows that I'll like";
}
_showDisplayOptionsActionSheet = [[UIActionSheet alloc] initWithTitle:#"Event Display Settings" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles: showTypes, #"Date Range", nil];
[_showDisplayOptionsActionSheet showFromTabBar:self.tabBarController.tabBar];
}
Per the comments.
Addendum 2
// All of DatePickerViewController.m
#import "DateRangeViewController.h"
#interface DateRangeViewController ()
#end
#implementation DateRangeViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
// All of DatePickerViewController.h
#import <UIKit/UIKit.h>
#interface DateRangeViewController : UIViewController
#end

I would disconnect the DatePickerViewController from the Nav controller and leave it as a stand alone VC.
Then get rid of showDateSelect and change clickedButtonAtIndex method to the following:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if( buttonIndex == 1 )
{
pickerView = [[DatePickerViewController alloc] init ];
[self.navigationController pushViewController:pickerView animated:YES];
}
}
Here you are pushing the pickerView unto the Navigation controller stack. You will need to pop it to remove it.
you can also experiment with present/dismiss but I am not sure whether it would work on Nav Controller stack.
Update:
I am not sure why you are re-declaring the viewDidLoad in your DatePickerViewController.h file. Take that out.
Also in all view Did/Will Load/Appear methods, you need to start by calling super. So insert [super viewDidLoad] as a first line in your viewDidLoad method of DatePickerViewController.m

Figured it out. The problem is that when you're using a storyboard, like I am, and you're programmatically presenting a view, you need to begin by creating a storyboard object:
// "MainStoryboard_iPhone" is your .storyboard file's name
// [NSBundle mainBundle] returns the main storyboard bundle.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone"
bundle:[NSBundle mainBundle]];
Next, you instantiate the view that you want to present using that storyboard object:
// Give your view an identifier in the storyboard. That's how the storyboard object will find it.
// You should see it in the right panel options when you click on the view.
UIViewController *dateRangeController = [storyboard instantiateViewControllerWithIdentifier:#"dateRange"];
Present.
[self presentViewController:dateRangeController animated:YES completion:nil];
My mistake was in thinking that if you simply associated the view with the viewcontroller file (under the Class property in the config pane), that's all that was needed. But if you're using a storyboard, the UI process is expecting views to be instantiated through it, whether automagically or programmatically (presumably because more than one view can use the same view controller).

Related

Create a Logout Button

NB: First App;
I am currently trying to create a log out button for an app that I have created. Essentially, on load, the user is presented with LoginViewController.xib with 2 text fields and a button, given that the text fields meet the arguments, when the button is pushed, the following argument is executed:
if (success) {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate.window setRootViewController:appDelegate.tabBarController];
}
This works fine, and the user is taken into the app with a tab controller that switches between 3 xib's (Home, Settings, Table).
In the settings tab, I have a button "LogOut" which when pressed, I would like the user to be returned to the "LoginViewController.xib" but I can't seem to find any way of doing this from tutorials on youtube or on the web.
Please see below for the Settings coding;
SettingsViewController.h:
#import <UIKit/UIKit.h>
#interface SettingsViewController : UIViewController
- (IBAction)LogOutClick:(id)sender;
#end
SettingsViewController.m:
#import "SettingsViewController.h"
#interface SettingsViewController ()
#end
#implementation SettingsViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)LogOutClick:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
Calling dismissViewControllerAnimated: is not going to help as your tab bar controller/settings view controller is not presented by any view controllers. The original login controller, as far as I can tell from the code you have supplied, no longer exists in memory and, as a result, you can not "return" to it.
You have two options: one is to present the login view controller on the tab bar controller, and the other is to change the window's root view controller to the login controller. For example
//Present over tab bar
- (IBAction)LogOutClick:(id)sender {
LoginViewController *loginController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self presentViewController:loginController animated:YES completion:nil];
}
//Switch root view controller
- (IBAction)LogOutClick:(id)sender {
LoginViewController *loginController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[appDelegate.window setRootViewController:loginController];
}
In my opinion, the first method is the better way of doing things. After the login controller has been dismissed, you can then populate the tab bar controller's views with the proper data. However, as the login controller already has a method that switches the root view controller of the window, it may be easier for you to just switch the root view controller back to the login controller.

Xcode - how to dismiss modal UIImageView?

Very new to iOS development. I'm trying to make an thing that will let me select a photo from the photo library, and display it. I'm using a storyboard with a navigation controller. I'm able to select and display the image just fine, but I can't figure out how to dismiss the imageview and return back to the image picker. I just get a fullscreen image and cant click anywhere to do anything. Code looks like this:
snapViewController.h
#import <UIKit/UIKit.h>
#interface snapViewController : UIViewController <UIImagePickerControllerDelegate,
UINavigationControllerDelegate>
#property (strong, nonatomic) IBOutlet UIImageView *ImageView;
- (IBAction) done:(UIStoryboardSegue *)unwindsegue;
#end
My .m
snapViewController.m
#import "snapViewController.h"
#interface snapViewController ()
#end
#implementation snapViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (BOOL)prefersStatusBarHidden {
return YES;
[self setNeedsStatusBarAppearanceUpdate];
}
- (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (self.ImageView.image == nil){
UIImagePickerController * imagePickerController = [UIImagePickerController new];
imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext;
imagePickerController.delegate = self;
[self presentViewController:imagePickerController animated:NO completion:nil];
}
else {
}
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
self.ImageView.image = image;
[self.ImageView setUserInteractionEnabled:YES];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction) done:(UIStoryboardSegue *)unwindsegue {
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Storyboard looks like this, I stuck a toolbar on the bottom of the UIImageView with a done button:
Do I need to bind the done button to the [self dismissViewControllerAnimated:YES completion:nil]; action? How can I do this? I've been struggling with this for a couple hours and can't figure it out. Thanks.
Read up a bit on views and windows and view controllers and stuff, came up with this:
- (IBAction)kill:(id)sender {
self.ImageView.image = nil;
[self viewDidAppear:TRUE];
[self dismissViewControllerAnimated:NO completion:nil];
}
I bound my done button to it and it looks like it works. It seems to be acceptable memory wise(opening and closing images multiple times doesn't ever make memory increase over what it was originally). It if this is not the right way to do it please let me know. Thanks.
Modal segues are very special compared to other segues in the sense that when you dismiss the new view controller, you call a method from the old one. The way it works is like this:
In the old view controller create a method of type (IBAction*) with argument (UiStoryBoardSegue*). Example:
-(IBAction*)done:(UIStoryBoardSegue*)segue{}
Then, in the story board, from your "Done" button in the new view controller control-drag to the green (left most) button in the line on the bottom of the new view controller (same view controller as the Done button), the method you created in the old view controller will popup, and you need to select it.
What will happen now is that once the done button is pressed, the modal will dismiss and will call the Done method on the old view controller
You are on the right track with your kill IBAction however there's a few things you don't need in there. You should not be calling viewDidAppear from an action. Let the OS manage all calls to viewDidLoad, willAppear, didAppear, etc.
If you want to remove the imageview's image from displaying, then you simply need to call self.imageView.image = nil;. You could then add another button and hook it up to an action to open the image picker modal and select a different image.
Remember that snapViewController is not a modal, it is simply the root view controller in the navigation stack that happens to have a full-screen image on it. The Navigation controller is a component that holds a bunch of view controllers -- it itself is never displayed to the user. That said, you do not need to dismiss snapViewController to reset the image, you should just need a done or kill action that looks like this:
- (IBAction)kill:(id)sender {
self.imageView.image = nil;
}

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
}

Push View Controller, Black Screen

I'm pushing to a new view controller and passing some data to it. When I run the application I can press the button and push to a new view but the screen is completely black. Any help is appreciated.
- (IBAction)button:(id)sender {
NSString *firstField = self.field.text;
NSString *secondField = self.field2.text;
self.resultsArray = [[NSArray alloc] initWithObjects:firstField, secondField, nil];
NSUInteger randomResult = arc4random_uniform(self.resultsArray.count);
self.label.text = [self.resultsArray objectAtIndex:randomResult];
ImagesViewController *ivc = [[ImagesViewController alloc] init];
ivc.label = self.label.text;
[self.navigationController pushViewController:ivc animated:YES];
}
When you're using a storyboard, and you want to push a view controller in code (rather than with a segue), you need to give the controller an identifier, and create it like this:
ImagesViewController *ivc = [self.storyboard instantiateViewControllerWithIdentifier:#"MyIdentifier"];
ivc.label = self.label.text;
[self.navigationController pushViewController:ivc animated:YES];
The view controller you are pushing is not having any frame dimension set.It is always recommended to call designated init for objects. For view controllers, designated init method is
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
if you have a xib assign it like
ImagesViewController *ivc = [[ImagesViewController alloc] initWithNibName:<your xib> bundle:[NSBundle mainbundle];
if you are using custom view, assign a frame dimension and add it as subview
Xcode 7.3 and Swift 2.2
In my case, I had made changes in the storyboard and made it a TabBarController and accordingly changed the class of the controller from UIViewController to UITabBarController. After some tweaking, this change wasn't favourable and I un did all the changes and got a black screen then because I had forgotten to change the class of the controller. I changed it back to UIViewController and it started working again.
So check if you have made the same mistake. The black screen came because the storyboard had a class(UIView/UITabBar/UITableView Controller) but that wasnt the same in code.
This can also happen if you have somehow got an incorrect connection between one of the subviews in the storyboard to the controller's view. Check the Referencing Outlets are correct in each of your subviews.
I got a good one:
Make sure you are implementing the right Super class, delegate, etc.. in the top part of the viewController you are trying to present. i.e.
I wasn't using/implementing UINavigationController at all
class TMDetailBlogViewController: UINavigationController {
//code goes here
}
After
class TMDetailBlogViewController: UIViewController {
//code goes here
}
Typically, you transition to another view controller by calling:
initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
on your custom UIViewController.
If you're not using a xib file, then what you're doing may be fine. Are you dynamically creating your UI elements within the constructor of your ImagesViewController?
I was trying without using storyboard, and its just that the default screen it uses is in black color. I changed the background color to white and it worked.
Pushed the controller this way-
NextController *nextController = [[NextController alloc]init];
[self.navigationController pushViewController:nextController animated:YES];
In NextController-
(void)viewDidLoad{
[self.view setBackgroundColor:[UIColor whiteColor]];
}

Return to ViewController other than the ParentViewController on dismissal of ModalViewController

First, have a look at this screenshot of my storyboard:
It is an application for a sound map. The user can either record a new field recording or chose an existing one from the library and upload them. The ViewController where the user has to add a title/description etc. (or modifies them when coming from the library) is presented modally (on the top right).
If the user choses to cancel this and to delete the recording, he shall return to the recording screen, if he comes from there, otherwiese to the library. If he choses to save/upload the recording, he shall return to the library, where the upload progress will be displayed.
How can I come back to the desired ViewController independently of the ParentViewController that I come from?
I thought about unwindSegue, but that doesn't work for my layout. Then I figured out a dirty workaround where I changed the selected tab of the TabBarController. But then I also want to set up the delegate correctly to pass some data.
Any thoughts are welcome!
I don't think changing the selectedIndex of the tab bar controller is a "dirty workaround" -- that's the way to do it, given your set up. It appears that you would want the EntryViewController (Library) to be the delegate of the EntryDetailViewController. If that's so, you could set up the delegate in the viewDidLoad method of the EntryDetailViewcontroller like this:
#import "DetailViewController.h"
#import "TableController.h"
#interface DetailViewController ()
#property (strong,nonatomic) UITabBarController *tbc;
#end
#implementation DetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tbc = (UITabBarController *)self.presentingViewController;
self.delegate = (TableController *)[(UINavigationController *)self.tbc.viewControllers[1] topViewController];
}
- (IBAction)saveAndUpload:(UIButton *)sender {
[self.tbc setSelectedIndex:1];
[self.delegate saveRecording:#"test recording"];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (IBAction)delete:(UIButton *)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
In my example, I just have two buttons to either save or cancel, and my TableController would be the same as your Library controller.

Resources