I'm very new to Xcode and have run into some difficulties when opening new views.
I currently use this way to open a new view. It is stored in a utilities NSObject class:
+ (void)OpenSettings:(UIViewController *)VC{
Settings *settings = [[Settings alloc] initWithNibName:#"Settings" bundle:nil];
second.userID=[[[NSUserDefaults standardUserDefaults] objectForKey:#"UserID"] integerValue];
[VC.self presentViewController:settingsInst animated:NO completion:nil];
}
Is there anyway that I can open a view WITHOUT having to pass in the current view controller? It's not of huge concern but to me makes it look a little untidy when having to call the following in the classes that want to open up my settings view:
[Utilities OpenSettings:self];
(1) Because you said that you are new to Xcode: do you know Storyboards? Please have a look at it if you haven't already: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/RoadMapiOS/SecondTutorial.html
As far as I understand, you are trying to encapsulate the "push settings view" functionality which could be very easy be made with segues in storyboards.
(2) If you do not want to use storyboards for whatever reason, you could write your own superclass that inherits from UIViewController:
MyViewController.h
#interface MyViewController : UIViewController
#end
MyViewController.m
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void) openSettings
{
Settings *settings = [[Settings alloc] initWithNibName:#"Settings" bundle:nil];
settings.userID=[[[NSUserDefaults standardUserDefaults] objectForKey:#"UserID"] integerValue];
[self presentViewController:settings animated:NO completion:nil];
}
#end
Any of your ViewController that inherit from MyViewController will now be able to use
[self openSettings]
Keep in mind, that UITableViewController is not a subcalls of UIViewController. If you want the same functionality here, you also have to create a custom MyTableViewController.
Exact solution depends on your view controller hierarchy. I'm assuming you have a UINavigationController on your active window (which are probably created in AppDelegate):
[self.navigationController.topViewController presentViewController:settings animated:NO completion:nil];
will do it. If you can post your main view structure, we can help more (Are you using a UITabBarController or UINavigationController or just a view etc.?)
Related
I'm new to iOS development.
I have a ViewController ViewController with a button. When the user presses that button, I want to switch the view to RegisterViewController.
ViewController.m contains following code:
#import "ViewController.h"
#import "RegisterViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize registerViewButton;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)registerViewButtonClick:(id)sender {
NSLog(#"registerViewButtonClick called");
RegisterViewController* controller = [[RegisterViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
}
#end
According the debug output, method registerViewButtonClick is actually called, when I press the button.
But the view represented by RegisterViewController doesn't appear.
The code of the application is available here.
What do I need to change in order for the RegisterViewController's view to become visible, when the button is pressed?
I ran your code and found that there is no navigation controller implemented in your code but you are trying to push the registerViewController. Try to present the viewcontroller like below:
[self presentViewController:controller animated:YES completion:nil];
instead of
[self.navigationController pushViewController:controller animated:YES];
The pushViewController works only when there is a UINavigationController. As per the documentation, The UINavigationController class implements a specialized view controller that manages the navigation of hierarchical content. Since, there is no UINavigationController in your code (self.navigationController is nil in this case), nothing happens when you try to push the viewController.
UINavigationController also comes handy when you want to maintain a stack of viewControllers wherein you can push or pop as per the need. This also gives you 'Back' button automatically. If your need is just to present a viewcontroller, then presentViewController: can be the right option.
If you want to present then no need of UINavigationalController.
But for pushing UINavigationalController is must .
In Storyboard, you have to add one UINavigationalController.
In Button Actions initialise the VC correctly with nib Name.
RegisterViewController* controller = [[RegisterViewController alloc] initWithNibName:#"RegisterViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
You might need to initialize the UIViewController with the nib name..
like this
RegisterViewController* controller = [[RegisterViewController alloc] initWithNibName:#"RegisterViewController" bundle:nil];
RegisterViewController *news=[[RegisterViewController alloc] initWithNibName:#"RegisterViewController" bundle:nil];
[self.navigationController pushViewController:news animated:YES];
[news release];
If you are using storyboards then you need to get your view controller from the storyboard like
RegisterViewController *viewController = (RegisterViewController*)[[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"vc-identifier"];
Replace vc-identifier with whatever you have given as the identifier in storyboards and replace MainStoryboard with the name of your storybaord.
then pass it into your navigation controller like you are already doing, that is as long as your navigation controller isn't nil
[self.navigationController pushViewController:controller animated:YES];
Just been told that you aren't using storyboards, I was unable to download your link because of my location so I gave a solution with storyboards. My recommendation would be to move to using storyboards.
Will leave this here as it may help others who are having the same problem but are using storyboards
I was thinking about this today, and now i've tested i'm a little confused…
When using viewControllers either by pushing a viewController onto the Navigation Stack or Presenting a ViewController modally I'm wondering about memory management.
Lets use the modal example as a thought experiment, here is the source to create and present the view, in my example it doesn't matter if ARC or not so here's both:
With ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Without ARC:
ViewController *myViewController = [[ViewController alloc] init];
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
[myViewController release]; //As it's now 'owned' by the presenting View controller
This would be my understanding about how to present a viewController modally over an existing ViewController.
Lets say for our example the above code resides in a method which is called when a button is touched to present the ViewController.
Now to my question,
What I am doing is calling this code each time a button is touched, During testing with Instruments I didn't seem to have any leaks. - However because I have NSLog statements in the myViewController dealloc & viewDidLoad methods I know that it's getting instanciated everytime I touch the button but never deallocated.
So...
A) Why am I not getting a leak showing (or a rise in Live Bytes) in instruments (when either using ARC or not) because I am seemingly creating a new viewController and leaking the old one each time I go to present it.
B) What is the correct way to write the above code if this is not memory safe? I see this kind of code snippets all over Apple's example code and internet. Should I (and they) not be wrapping the alloc init line in an if statement to check if the object is already created?
i.e.
if(!myViewController)
{
ViewController *myViewController = [[ViewController alloc] init];
}
myViewController.delegate = self;
[self presentViewController:myViewController animated:YES completion:NULL];
Thanks for taking the time to read and answer, I really wonder about this as I've been creating, pushing and presenting ViewControllers using the above code the whole time, and never noticed a leak! - might have to go back and rewrite it all!
To avoid confusion please note: The delegate property is a custom property of my UIViewController subclass (where I've implemented a delegate protocol), required to dismiss the Modally present Viewcontroller properly. As per coding guidelines.
Regards,
John
EDIT As Requested, Creation of the delegate:
.h
#protocol NotificationManagementViewControllerDelegate;
#interface NotificationManagementController :
{
__weak NSObject <NotificationManagementViewControllerDelegate> *delegate;
}
#property (nonatomic, weak) NSObject <NotificationManagementViewControllerDelegate> *delegate;
#protocol NotificationManagementViewControllerDelegate <NSObject>
#optional
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController;
.m
- (void)sendMessageToDismiss {
if ([[self delegate] respondsToSelector:#selector(didFinishSettingNotification:)]) {
[self.delegate didFinishSettingNotification:self];
}
}
And finally in the delegates .m:
- (void)didFinishSettingNotification:(NotificationManagementController *)notificationManagementController
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
You are not getting a leak because you create a new controller and ARC will release this allocation for you.
But, it's better to create a #property for your new view controller.
and modify your i.e. implementation like :
#property (nonatomic, strong) ViewController *myViewController;
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
self.myViewController.delegate = self;
[self presentViewController:_myViewController animated:YES completion:nil];
Here, you have a lazy property and you don't create a new one ViewController after the first creation.
But, you need to pass your delegate (or any property) outside your test.
Furthermore, if you use your first implementation and add this controller in a subview of the current controller without property, this will work but you will get a leak.
I got this experience with the code below :
RootViewController
- (void)viewDidLoad
{
[super viewDidLoad];
ViewController *myViewController = [[ViewController alloc] init];
[self.view addSubview:myViewController.view];
}
myViewController will be add on the screen but released immediately without keeping any reference of the object, so if you add an action in 'ViewController`, your application will crash without explanation of XCode.
So, the correct way to write this without leak will be :
- (void)viewDidLoad
{
[super viewDidLoad];
if (!_myViewController)
self.myViewController = [[ViewController alloc] init];
[self.view addSubview:self.myViewController.view];
}
The answer is a bit longer and can be improved so don't hesitate !
Hope it's going to help some people.
I am trying to figure out for a while now, how should I use the annotation button to switch
to another mapView. The app I am working on uses MapBox - maps. I checked the exemples
provided by them, but programmatically switching between two maps is there always achieved
through tab bar (which is not the case I want to use).
I am working with storyboards and I understood it quit well, how the segue should be made
in the Interface builder, but I think I am not managing with the programmatically integrated
buttons on map views. I initiated 'id's in both header files and I proclaimed them in the
Identity Inspector as well.
This is the part of the code, where I implement the RMMMapView with the annotation in the
main View Controller - ViewController and it works perfectly:
- (void)viewDidLoad{
[super viewDidLoad];
RMMapBoxSource *onlineSource = [[RMMapBoxSource alloc] initWithMapID:(([[UIScreen mainScreen] scale] > 1.0) ? kRetinaMapID : kNormalMapID)];
_mapView = [[RMMapView alloc] initWithFrame:self.view.bounds andTilesource:onlineSource];
_mapView.tileSource = [[RMMapBoxSource alloc] initWithMapID:(([[UIScreen mainScreen] scale] > 1.0) ? kRetinaMapID : kNormalMapID)];
_mapView.centerCoordinate = CLLocationCoordinate2DMake(0,0);
_mapView.adjustTilesForRetinaDisplay = YES;
_mapView.zoom = 4;
_mapView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
[self.view addSubview:_mapView];
_mapView.showsUserLocation = YES;
[_mapView setConstraintsSouthWest:[_mapView.tileSource latitudeLongitudeBoundingBox].southWest
northEast:[_mapView.tileSource latitudeLongitudeBoundingBox].northEast];
RMPointAnnotation *annotation = [[RMPointAnnotation alloc] initWithMapView:_mapView
coordinate:_mapView.centerCoordinate andTitle:#"Hello, world!"];
[_mapView addAnnotation:annotation];
}
and this is the part, where I try to call the LowContentMap viewController, from the ViewController - main ViewController:
- (void) prepareForSegue:(UIStoryboardSegue *) segue sender:(id) sender {
if ([segue.identifier isEqualToString:#"Hello, world!"]) {
//LowContentMap *lowContentMap = segue.destinationViewController;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:[NSBundle mainBundle]];
LowContentMap *lowContentMap = [storyboard instantiateViewControllerWithIdentifier:#"lowContentMap"];
lowContentMap.lcm = _vc;
}}
This is the part of the code, that should be filled in:
- (void)mapView:(RMMapView *)mapView annotationView:(RMPointAnnotation *)annotation calloutAccessoryControlTapped:(UIControl *)control{
[self performSegueWithIdentifier:#"ShowSomeViewController" sender:annotation];
}
It would be really great, if somebody would try to resolve the problem.
I followed the discusion between Noa and Kronos at:
Setting up a detail view controller using a segue
but I still think, the part with the 'id' is something I am doing wrong. Thanks in advance.
1. I think your problem is that you don't know how to display another viewController
You should give the "View Controller Programming Guide" a good read, especially the part "Presenting View Controllers from Other View Controllers"
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
I also recommend to check out some of the awesome WWDC videos that show how to use ViewControllers.
2. How to create and display a viewController
There are many ways to do it. E.g. using the storyboard to create and display the viewController, define a protocol in the child controller, override "performSegueWithIdentifier" to set the delegate and implement the protocol to dismiss the vc.
However, in your case, it seems to make sense to do it all programmatically. Thus, you need to:
a) find the right place to add your action
b) alloc and init your view controller:
MyController *myController = [[MyController alloc] init];
// setup as required, there should be at least a delegate (being able to dismiss the view)
myController.delegate = self;
If you have designed your viewController in a storyboard, you might want to use this alloc/init routine instead:
MyController *myController = [[UIStoryboard storyboardWithName:#"Main.storyboard" bundle:nil] instantiateViewControllerWithIdentifier:#"MyController"];
myController.delegate = self;
c) display your new view controller;
this depends on if you have a navigationController:
[self.navigationController pushViewController:myController animated:YES];
...or if you want to present it modally:
[self presentViewController:myController animated:YES completion:NULL];
d) dismiss when done;
when your other controller is done with whatever it does, it should inform its delegate (implement your own protocol!) that it's done and should be dismissed. The delegate is the original viewController that created (alloc'd/inited) the "myController":
// this method should be defined in a protocol and implemented in the vc that created (and owns) the child view controller
// typically, this is the parent view controller
- (void)done {
[self dismissViewControllerAnimated:YES completion:NULL];
}
if you used a navigationController it's not -dismissViewControllerAnimated:, but
[self.navigationController popViewControllerAnimated:YES];
hope this helps to clarify things
I have a custom view controller named CKCalendarViewControllerInternal.
CKCalendarViewControllerInternal
This class is the subclass of UIViewController.
CkCalendarViewController
I have a custom view controller named CKCalendarViewController. It's a subclass of UINavigationController as follow:
#interface CKCalendarViewController : UINavigationController <CKCalendarViewDelegate, UINavigationControllerDelegate>
This class is initialize with the CKCalendarViewControllerInternal as follow:
- (id)init
{
CKCalendarViewControllerInternal *calendarViewController = [CKCalendarViewControllerInternal new];
self = [super initWithRootViewController:calendarViewController];
}
Now, In AppDelegate my first view is as follow:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.viewController = [[[ViewController alloc] initWithNibName:#"ViewController" bundle:nil] autorelease];
UINavigationController *n1=[[UINavigationController alloc]init];
n1.viewControllers=[[NSArray alloc]initWithObjects:self.viewController, nil];
self.window.rootViewController=n1;
[self.window makeKeyAndVisible];
return YES;
}
CkDemoViewController
This class is the subclass of CkCalendarViewController as follow
#interface CKDemoViewController : CKCalendarViewController
ViewController.m
When i try to push the CKDemoViewController on button clicked.
Error & Question
It shows me error like
Exception: Pushing a navigation controller is not supported
Exception: [NSException]:Pushing a navigation controller is not supported
ex.name:'NSInvalidArgumentException'
ex.reason:'Pushing a navigation controller is not supported'
Reason for error
This is because the CKCalendarViewController is the subclass of UINavigationController.
If i try to open the modal view, it works perfectly.
But How can i initialize the CKCalendarViewController as shown above with the CKCalendarViewControllerInternal class??
Thank you,
Answer will greatly appreciate
If I understand correctly what you are doing, the simplest "hackish" way to make things work would be making CKCalendarViewController derive from CKCalendarViewControllerInternal. I am suggesting this because I see that you are trying to use your CKCalendarViewController as a normal view controller, so there should be no reason to have it be a navigation controller.
Another possibility would be for you to actually use your CKCalendarViewController as a navigation controller by doing this in your app delegate:
UINavigationController *n1 = [[CKCalendarViewController alloc]init];
n1.viewControllers = [[NSArray alloc]initWithObjects:self.viewController, nil];
self.window.rootViewController = n1;
but this depends on what you are trying to achieve.
More generally, if you are interested in "nesting" controllers within controllers, you should learn about controller containment. In controller containment, what you do to add a controller to another one is basically this:
[vc willMoveToParentViewController:self];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
This question might have been answered, if yes, please share the link.
I have created a Single View Application, It works fine, but now I have added a new view and on a button click, wants the new view to appear.
This is the code for click action,
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:settingsViewController animated:YES];
The Default ViewController now looks like this in .h file
#interface ViewController : UIViewController<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
The SettingsViewController.m has a default
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{}
Can I add another view to "Single View Application" like this or should I chose another template for my project ?
Thanks.
You need to create a UINavigationController in your AppDelegate. Then make your ViewController the rootViewController of the UINavigationController. Then you will be able to push and pop views.
Here is the code to create the rootViewController where mainNavigationController is the UINavigationController in your AppDelegate:
ViewController *vc = [[ViewController alloc] init];
mainNavigationController = [[UINavigationController alloc] initWithRootViewController:vc];
Once you have the ViewController set up as the rootViewController it will conform to the UINavigationController push and pop methods to create a stack of UIViewControllers.
That is fine. The single view application template is just a barebones template. You can add any type of navigation you like to it.
In iOS 5, switching between views works a bit different i think,
I have created a few apps with the above mentioned code for switching views.
But now, I have to write it like this to work:
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:[NSBundle mainBundle]];
[self presentModalViewController:settingsViewController animated:YES];