I have an app based on a tabBar controller. Within a certain view I'd like to add swipe gesture recognition and swap the current view with another one (which is not part of the tabBarController array). I have tried:
- (IBAction)swipeLeftDetected:(UIGestureRecognizer *)sender
{
//Does not work
UIViewController *DesiredViewController =[[UIViewController alloc] initWithNibName:#"DesiredViewController" bundle:nil];
DesiredViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:DesiredViewController animated:YES];
[self.view addSubview:DesiredViewController.view];
}
but the program crashes. The error I get is related to a SegmentedControl which is present in the next view but is absent in the current one. The views independently work perfectly!
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:'[<UIViewController 0xa355fb0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key X_SegmentedControl.'
I don't understand what I am doing wrong.. I want to swap the views completely rather than putting one on top of each other. Any advice please? Thanks
instead use this
-(IBAction)swipeLeftDetected:(UIGestureRecognizer *)sender
{
DesiredViewController *objView =[[DesiredViewController alloc]initWithNibName:#"DesiredViewController" bundle:nil];
objView.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:objView animated:YES];
[objView release]; // use release if using Non-ARC
}
The problem is not about the transition.
You are loading a view controller from a NIB.
A view controller is usually subclassed, so (assuming that your nib is configured correctly and you have .h and .m implementation file for your DesiredViewController subclass) you should init like this:
DesiredViewController *controllerInstance =[[DesiredViewController alloc] initWithNibName:#"DesiredViewController" bundle:nil];
The exception is because you probably have a segmented control inside your view controller subclass, Xcode is trying to link this control to the outlet on the view controller, but this outlet doesn't exists (because you are allocating an UIViewController not the subclass).
Related
I am making an application where there are multiple pins in a map. (using xcode for iOS apps) When I click on the pins, the callout comes up with a disclosure button that when pressed brings up a new viewcontroller (which I use as the detail view.. is this correct?)
I am currently having problems going back to the original viewcontroller after viewing the new viewcontroller.
How should I proceed to go back to the map view?
I have tried with the -(IBAction)Back; command and linking it to a button on the new viewcontroller, however when I click it in the simulator, a black screen comes up and no error is shown in the output..
Any help would be appreciated!
I used the following to view the new view controller:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if ([view.annotation.title isEqualToString:#"..."]) {
...Controller *sampleView = [[...Controller alloc] init];
[self presentModalViewController:sampleView animated:YES];
}
if ([view.annotation.title isEqualToString:#"..."]){
...ViewController *sampleView = [[...ViewController alloc] init];
[self presentModalViewController:sampleView animated:YES];
}
}
EDIT 1:
This is the error code I get after making the change..
2013-06-30 18:02:30.386 lam[15156:13d03] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "...Controller" nib but the view outlet was not set.'
*** First throw call stack:
(0x1e2d012 0x126ae7e 0x1e2cdeb 0xf88c8 0xf8dc8 0xf8ff8 0xf9232 0x104c25 0x3043a3 0x101ee3 0x102167 0xfee0071 0x374c 0x10deb1a 0x10ea28e 0x82f617f 0x127e705 0x1b2c0 0x1b258 0xdc021 0xdc57f 0xdb6e8 0x4acef 0x4af02 0x28d4a 0x1a698 0x1d88df9 0x1db0f3f 0x1db096f 0x1dd3734 0x1dd2f44 0x1dd2e1b 0x1d877e3 0x1d87668 0x17ffc 0x2842 0x2775)
libc++abi.dylib: terminate called throwing an exception
How did your calloutAccessoryControlTapped "bring up" this new view controller?
If you used a modal transition (e.g. such as presentViewController or modal segue), then you would dismiss it with dismissViewControllerAnimated. If you used the deprecated presentModalViewController, then you'd use the dismissModalViewControllerAnimated, but then again, you probably shouldn't use the deprecated methods unless you need to support iOS versions prior to 5.0. Use the presentViewController and dismissViewControllerAnimated renditions, instead. Anyway, you might end up with an IBAction method like:
- (IBAction)handleDoneButton:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
If you used push transition (e.g. pushViewController or push segue), then you'd generally just avail yourself of the built-in "back" button, but if you need your own button to pop this controller, you'd use an IBAction with popViewControllerAnimated. For example:
- (IBAction)handleDoneButton:(id)sender
{
[self.navigationController popViewControllerAnimated:YES completion:nil];
}
Or, if your calloutAccessoryControlTapped used a performSegueWithIdentifier, then you could either use the above technique or, in iOS 6 and above, you could define the following unwind segue in the view controller that has the map:
- (IBAction)backToMap:(UIStoryboardSegue *)segue
{
// do whatever you want
}
And then you can control-drag from the button to the exit outlet in the bar under the scene, and you should see this backToMap unwind segue.
Regarding your error, it means that your NIB's root view is not set. Select the view and look at its outlets. You should see something like:
If not, (a) make sure you set your "File's owner" to be your view controller class; and (b) right click on "File's owner"; (c) drag from the "o" next to the "view" in the popover view to the actual view:
Also, when creating the view controller, you'd probably want to specify which NIB to use:
ViewController *controller;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
controller = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
} else {
controller = [[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
Problem: On selection of a particular uitableviewcell, a new DetailViewController should be opened. I have written the code in didSelectRowAtIndexPath but when I click on the cell it is showing me a runtime error.
I have tried calling with [self.navigationController presentModalViewController:jacket animated:YES ]; but when I do my view controller is not getting opened.
Another method I tried is by [self.navigationController performSegueWithIdentifier:#"JacketDetails" sender:self ];.
I have Specified Identifier in the segued has "JacketDetails" in the inspector but here I am getting a run yime error. I have Hooked Segue from UITABLEVIEWCELL to VIEWCONTROLLER.
When I click on the row JacketDetailViewController Should be open. I have Created Class JacketDetailViewController and for New ViewController I have set the class for this in the inspector.
I don't know why it is showing no segue , I have given the identifier in the inspector and properly hooked from tableviewcell to new view controller.
In JacketDetailViewController I want to display a list of Jackets. Presently it is blank ViewController.
My code is below. Would you please suggest a solution? I am a self-teaching beginner in this field. I might have made some minor mistakes. I have Googled my problem and tried to solve it but I have been stuck here for a few days.
TshirtDetailViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
[tableView deselectRowAtIndexPath:indexPath animated:YES ];
JacketDetailController *jacket =[[JacketDetailController alloc]init];
NSInteger index =indexPath.row;
NSLog(#"Row:%d",index);
NSString *titleString = [[NSString alloc] initWithFormat:[jackets objectAtIndex:indexPath.row]];
NSLog(#"%#",titleString);
jacket.title=titleString;
// ...
// Pass the selected object to the new view controller.
[self.navigationController performSegueWithIdentifier:#"JacketDetails" sender:self ];
// [self.navigationController presentModalViewController:jacket animated:YES ];
}
#end
Error in Console:
2013-01-07 10:52:21.020 KidsShopee[617:f803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<UINavigationController: 0x6a5fac0>) has no segue with identifier 'JacketDetails''
*** First throw call stack:
(0x13bf052 0x1550d0a 0xdd24b 0x3e8e 0xa671d 0xa6952 0x92e86d 0x1393966 0x1393407 0x12f67c0 0x12f5db4 0x12f5ccb 0x12a8879 0x12a893e 0x16a9b 0x1b08 0x1a65 0x1)
terminate called throwing an exception(gdb)
Try to open view with these lines:
YoutubeViewController *objYoutubeViewController = [[YoutubeViewController alloc]initWithNibName:#"YoutubeViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:objYoutubeViewController animated:YES];
[objYoutubeViewController release];
In place of YoutubeViewController give your view controller class name & change these lines
According to you & check.Ok if you using storyboard then try like below:
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
bookmarkViewController *myVC = (bookmarkViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"bookmarkViewController"]
[self presentModalViewController:myVC animated:YES];
in place of bookmarkViewController give your controller name & check. in drag & drop view
controller in main storyboard give right class name like below images:
in place of my bookmarkViewController name give your view controller name that you want to open.
Don't call performSegueWithIdentifier:sender: on the navigation controller! Only your custom viewController can have segues. Call it on self.
Replace
[self.navigationController performSegueWithIdentifier:#"JacketDetails" sender:self ];
with
[self performSegueWithIdentifier:#"JacketDetails" sender:[tableView cellForRowAtIndexPath:indexPath]];
If you hooked up a segue in the storyboard from the table view cell to another controller, then you don't need any of this code at all to make the segue fire. You should be implementing prepareForSegue to pass the information you need to the detail controller, but no other code is necessary.
Based on the error you are getting "has no segue with identifier 'JacketDetails'", something is not right with your call to the segue. I have found that I have problems with seque names working properly unless I copy and paste from the name I gave the seque in the storyboard into my .m file. Even if the spelling and case is correct, I have found many times, copying and pasting has fixed the problem.
Can try with this-
JacketDetailController *jacket =[[JacketDetailController alloc]initWithNibName:#"JacketDetailController" bundle:nil];];///Change string as your nib name.
Then if its navigation base application-
[self.navigationController pushViewController:jacket animated:YES];
Then is its viewController base application-
[self presentModalViewController:jacket animated:YES];
I need some help in picking the 'right' solution for the following 'problem'.
I use the new storyboard feature to link all the screens of my application together. Basically the structure drills down to:
[Navigation Controller] => [View Controller #1] => [Tabbar Controller] => [View Controller #2]*
**(and some other tabs which are for now not important)*
I have attached a segue (push) from the first View Controller (#1) to the View Controller behind the Tab Bar Controller. This push is triggered when the users presses something on the first controller and works fine.
// Execute preset segue
[self performSegueWithIdentifier:#"segueEventDetail" sender:self];
When the user (which is now in the View Controller #2) presses the back button in the navbar the user goes back. Suppose he now triggers the segue again, the second view controller is shown again but is now 'resetted' (empty). (I believe after reading several fora and articles this is standard behavior when using segue's because these destroy and reinitiliaze the view controller's every time?)
This (the view controller being resetted) poses a problem because the contents of the second view controller is dynamic (depend on a JSON response from the server) and thus it is 'needed' that the view controller remains intact (or is restored) when the user comes back.
I have found several sources (see bottom) describing the same issue, but the solutions vary and I need some help picking the right one.
Summarize:
How can I 'retain'/save the state of a View Controller when the users presses back, while preserving the use of Storyboard & preferably also Segue's
Own Thoughts:
#1 I'm now thinking of caching the JSON Response to my singleton class (and from there to a PLIST) and checking within the second view controller if this data is present and than rebuild the view after which I check for any new data (resume normal operation).
#2 Another one I'm thinking of is 'bypassing' the segue and manually handle the switch of views , partially explained in (Storyboard - refer to ViewController in AppDelegate) - Is this also possible?
But maybe there is an easier/better option?
http://www.iphonedevsdk.com/forum/iphone-sdk-development/93913-retaining-data-when-using-storyboards.html
Storyboard - refer to ViewController in AppDelegate
How to serialize a UIView?
Yess!! I got the solution. Do the following:
In you're .h file:
#property (strong, nonatomic) UITabBarController *tabController;
In you're .m file:
#synthesize tabController;
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
The selected index is the tab you want to go
tabController.selectedIndex = 1;
[[self navigationController] pushViewController:tabController animated:YES];
For anyone coming across this (my) question in the future, this is how I ended up 'coding' it.
Open the storyboard and select your 'Tab Bar Controller' and open the Attributes Inspector
Fill in an 'identifier' in the field
With the first view controller (see scenario in original post) I create an global reference to the viewcontroller:
firstviewcontroller.h
#interface YourViewController : UIViewController {
UITabBarController *tabController;
}
firstviewcontroller.m
//Fill the reference to the tabcontroller using the identifier
tabController = [self.storyboard instantiateViewControllerWithIdentifier:#"tabbar"];
Now to switch from the firstviewcontroller the following line can be used:
[[self navigationController] pushViewController:tabController animated:YES];
This might be even more simple solution (without using properties - in fact, all your class instances don't need to know about their destination controllers, so just save it as static in the pushing function):
static UIVewController *destController = nil;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
if (!storyboard) {
DLog(Storyboard not found);
return;
}
if (destController == nil) { //first initialisation of destController
destController = [storyboard instantiateViewControllerWithIdentifier:#"{Your Destination controller identifyer}"];
if(!destController) {
DLog(destController not found)
return;
}
}
//set any additional destController's properties;
[self.navigationController pushViewController:destController animated:YES];
p.s. DLog is just my variation of NSLog.
But it's really interesting how to do this with segue?
I have a view that shows a map. I have a custom subclass of UIViewController (DetailViewController) that gets shown when the detailDisclosureButton of the callout above the pin is pressed. While in my map class, I create my detailview and add it to the subview like this:
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
[self.view addSubview:detailView.view];
[detailView release];
My DetailViewController has a TableView and parses the data in DetailViewController. However I get an error of sending the numberOfSectionsInTable message to a dealloc'd instance. I'm assuming it is this since I originally had this as a property and it worked fine with (nonatomic, retain). I'm assuming that I'm releasing it before the next view is done with it. If that is the case, when would I clean up the memory??? It seems like this would be the place to do it. Thanks.
I am not sure what makes you adding the view of DetailViewController into this mapviewcontroller's view. Don't you think right approach would be to either presentModalViewController or pushNavigationController?
DetailViewController *detailView = [[DetailViewController alloc] initWithNibName:#"DetailView" bundle:nil];
detailView.locationPoint = locationPoint;
detailView.locationCoordinate = locationCoordinate;
//[self.view addSubview:detailView.view];
[self.navigationController pushViewController:detailView animated:YES];
//OR
[self presentModalViewController:detailView animated:YES];
[detailView release];
You are getting the error because you are only using the view and deallocating the view controller immediately and hence tableview datasource and delegates are hitting a nil object.
Views do not retain their view controllers. Someone needs to retain the VC or else it will get released, and then the app will crash when the view makes a call into its delegate. When you use a navigation controller, the navcon has a stack of view controllers that it retains. Likewise with presentModalViewController, the OS takes care of retaining the detail VC.
Adding a detail view as a subview is not the normal way to navigate to a new view. Instead, one either uses a navigation controller and [navcon pushViewController::], or a modal subview and [self presentModalViewController::]. If the detail view occupies only a portion of the parent view, then it is normal to retain the view controller for the subview within the parent controller. That is, within the parent VC (your map class) add a property for the detail VC. Actually, it's more common to not even use a VC for a subview, but rather for screen-filling detail views.
I have a UITabBarConroller that I use to switch between 3 different views. This all works perfectly. On one of my tabs, I added a button at the to called "Add", I have added an outlet to this, as well as an IBAction method which looks like the following:
// Method used to load up view where we can add a new ride
- (IBAction)showAddNewRideView {
MyRidesViewController *controller = [[MyRidesViewController alloc] initWithNibName:#"AddNewRide" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
[controller release];
}//end showAddNewRideView
This currently works fine, and loads up my AddNewRide nib file. But, once that view loads, I have a cancel button, which, when clicked, I want to return to the previous view. So, I figured I would just do the reverse of the above, using the following method which would load back my previous nib:
- (IBAction)cancelAddingNewRide {
MyRidesViewController *controller = [[MyRidesViewController alloc] initWithNibName:#"MainWindow" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
[controller release];
}//end cancelAddingNewRide
But, which trying to load the MainWindow nib, the program crashes, and I get the following error:
2010-05-05 20:24:37.211 Ride[6032:207] *** -[MyRidesViewController cancelAddingNewRide]: unrecognized selector sent to instance 0x501e450
2010-05-05 20:24:37.213 Ride[6032:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[MyRidesViewController cancelAddingNewRide]: unrecognized selector sent to instance 0x501e450'
So, I am a little lost as to why it would work one way, but not the other.
First, I wanted to address part of the error: Think of your views as a stack. When you "push" a modal controller, you are adding that view to a stack. The old view is still there underneath. So you need to "pop" off the modal view to return to the old view. If you push a new view on, you now have 3 views on the stack which are all taking up memory, where you really only need one.
So, inside cancelAddingNewRide just try:
[super dismissModalViewControllerAnimated:true];
You may have other issues that are causing the crash, but this should generally get things working.
Typically when I have used presentModalViewController the presented viewController tells the calling viewController to dismiss it using dismissModalViewControllerAnimated:YES;
So in other words in the cacncelAddingNewRide you simply call the class that hass showAddnewRideView in it and have it pass itself to the method.
It's hard to explain but I'll show you an example:
cancelAddingNewRide class:
- (IBACtion)home:(id)sender {
if (self.delegate respondsToSelctor:#selector(dismiss:)]) {
[self.delegate dismiss:self];
}
}
and then in the showAddNewRideView class
-(void) dismiss:(cancelAddingNewRide_class *) controller {
[self dismissModalViewControllerAnimated:Yes];
}
Hope that makes sense and soz for typos
Edit: oh and make the controller's delegate self
controller.delegate = self;
Actually thinking about it more there is a bit more to this. You have to define the called viewController as a Delegate. Have a look at Stanford universities iPhone lectures, lecture 11 deals with this and is available from iTunesU.