This should be straight forward for a guru. I don't have any code really written out, just a couple of controllers and a custom UIView. All connected through nibs. The app loads without crashing, yet I can't see my NSLog() hit from my custom UIView.
My application delegate has default template code which calls for a class of mine called TabAnimationController. TabAnimationViewController has its view set to TabView. I made sure that in TabAnimationViewController's NIB that File's owner is set to TabAnimationViewController and that my instance of UIView has its class set to TabView.
In TabView.m I'm trying to see how NSLog is going to hit, and it's not showing up at all.
- (void)loadView {
NSLog(#"calling loadView");
}
- (id)initWithFrame:(CGRect)frame {
NSLog(#"Calling initWithFrame:");
return self;
}
Strange. I'm not sure why even after proper IB connections that my NSLog will not show up. Only anything put into drawRect: will invoke. Why isn't initWithFrame or loadView ever get hit? What if I want to customize this view programmatically?
First of all, when a view is dehydrated from nib file, instead of initWithFrame, initWithCoder is invoked. So you need to implement your initialization in initWithCoder as well. (It may be a good idea to keep the initWithFrame initialization as well, if you anticipate programmatically creating your TabView instead of hooking up in the IB. Just refactor your initialization to another method and call it from both implementations.)
Also in your initialization code above you must always call the super class's initialization. There is a boiler plate pattern all custom classes use in their init implementation for that:
if (self = [super initXXX]) { do your initialization }
return self;
Second, loadView which is actually a UIViewController method and not a UIView method is invoked only if the view outlet of the controller is nil.
Unless you are composing your view yourself programmatically using your controller, you do not need to override loadView. Instead you should override viewDidLoad, which is called after the view is loaded, to do additional initialization.
The simplest way to get this up and running is simply to use the "View based Application" template when you create a new project. It sets up everything you need to start with.
But, in short, you're looking at the wrong methods. First, you shouldn't override loadView unless you're creating your view programatically. If it's loading from a XIB file look at the initWithNibName method.
You might also want to look at the viewDidLoad, viewWillAppear and viewDidAppear methods that are triggered, well, it's fairly obvious when!
Related
I am developing an app that is a single NSWindow and clicking a button inside the window will present a NSViewController, and a button exists in that controller that will present a different NSViewController. I know how to swap out views in the window, but I ran into an issue trying to do this with the multiple view controllers. I have resolved the issue, but I don't believe I am accomplishing this behavior in an appropriate way.
I originally defined a method in the AppDelegate:
- (void)displayViewcontroller:(NSViewController *)viewController {
BOOL ended = [self.window makeFirstResponder:self.window];
if (!ended) {
NSBeep();
return;
}
[self.box setContentView:viewController.view];
}
I set up a target/action for an NSButton to the AppDelegate, and here's where I call that method to show a new view controller:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self displayViewcontroller:newVC];
}
This does work - it presents the new view controller's view. However if I then click any button in that view that has a target/action set up that resides within its view controller class, the app instantly crashes.
To resolve this issue, I have to change didTapContinue: to the following:
- (IBAction)didTapContinue:(NSButton *)sender {
NewViewController *newVC = [[NewViewController alloc] init];
[self.viewControllers addObject:newVC];
[self displayViewcontroller:[self.viewControllers lastObject]];
}
First of all, can you explain why that resolves the issue? Seems to be related to the way the controller is "held onto" in memory but I'm not positive.
My question is, how do I set this up so that I can swap out views from within any view controller? I was planning on getting a reference to the AppDelegate and calling displayViewcontroller: with a new controller I just instantiated in that class, but this causes the crash. I need to first store it in the array then send that reference into the method. Is that a valid approach - make the viewControllers array public then call that method with the lastObject, or how should this be set up?
What is interesting in your code is that you alloc/init a new view controller every time that you call the IBAction. It can be that your view its totally new every time you call the IBAction method, but I would think that you only have a limited number of views you want to show. As far as my knowledge goes this makes your view only to live as long as your IBAction method is long. That the view still exists, is because you haven't refreshed it. However, calling a method inside a view controller that is not in the heap anymore (since you left the IBAction method and all local objects, such as your view controller are taken of the heap thans to ARC) makes the app crash, because you reference a memory space that is not in use or used by something else.
Why does the app work when you ad the view to the viewcontrollers array? I assume this array is an array that has been initiated in the AppDelegate and now you add the view controller with a strong reference count to the viewcontrollers array. When you leave the IBAction method, the view controller still has a strong reference and ARC will not deallocate the view controller.
Is this the proper way? Well, it works. I would not think it is considered very good programming, since you don't alloc/init an object in a method that needs to stay alive after leaving the method. It would be better practice to allocate and initialize your view controller(s) somewhere in an init, awakeFromNIB or a windowDidLoad method of your AppDelegate. The problem with your current solution is that you are creating an endless array of view controllers of which you only use the last. Somewhere your program will feel the burden of this enormously long array of pretty heavy objects (view controllers) and will run out of memory.
Hope this helps.
By the way, this is independent of whether you use Mavericks or Yosemite. I was thinking in a storyboard solution, but that wouldn't answer your question.
Kind regards,
MacUserT
I'm new to iPhone development. I've been reading several questions on how to make a google maps annotation callout window accept line breaks. Every tutorial I've read requires me to fire the mapView:didSelectAnnotationView method. But I have no idea how to trigger this. things I've tried include
putting the method in my MapViewController.m file which extends UIViewController
putting the method in a MapView.m file which extends MKMapView, then have my Mapview element in my storyboard reference it as the class to use
There's so much about xcode, objective c, and iphone development that I don't understand, so i can't tell where my problem lies.
At the moment, my map does plot my desired marker on the desired location. I just need to understand how to fire the mapView:didSelectAnnotationView and mapView:viewForAnnotation functions before I can start customizing the call out box.
Does anyone have step by step instructions on how to trigger these functions?
A bit of background
A few things to note:
You don't call mapView:didSelectAnnotationView. The MKMapView calls that function on it's delegate. In other words, when you set up an MKMapView, you tell it: "hey, listen, anytimme you need to tell me what's happening on the map, go tell this guy, he'll handle them for you". That "guy" is the delegate object, and it needs to implement mapView:didSelectAnnotationView (that's also why its name "did select", ie, it already happened, as opposed to "select"). For a simple case, the delegate is often the UIViewController that owns the MKMapView, which is what I'll describe below.
That method will then get triggered when the user taps on one of your annotations. So that's a great spot to start customizing what should happen when they tap on an annotation view (updating a selection, for instance).
It's not, however, what you want if you want to customize what annotation to show, which is what it sounds like you're actually after. For that, there's a different method just a few paragraphs earlier on the same man page: mapView:viewForAnnotation. So substitute this method if you find that mapView:didSelectAnnotationView isn't what you were looking for.
What you can do
If you got as far as a map with a marker, I'm guessing you have at least:
* a view controller (extendeding from UIViewController, and
* an MKMapView that you've added to the view for that view controller, say named mapView
The method you want to fire is defined as part of the MKMapViewDelegate protocol.
The easiest way to get this wired is to:
make your UIViewController the delegate for you MKMapView
in code, say in your viewDidLoad, of your MapViewController.m you could do mapview.delegate = self, OR
in Interface Builder, you could drag the connection from the the MKMapView delegate property to the file's owner
then, define a method on your UIViewController called mapView:didSelectAnnotationView, declaring it just like the protocol does, in your MapViewController.m file:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// whatever you need to do to your annotation and/or map
}
Good luck!
mapView:didSelectAnnotationView is a delegate method of the map view, you can read about it here:
MKMapViewDelegate Protocol Reference
You don't need to call it, the map view will call it "by it self" and send it to every view/view controller that registered as it's delegate.
What do you need to do
Basically you need to add the MKMapViewDelegate on your .h file, what will look something like this:
#interface someViewController : UIViewController <MKMapViewDelegate>
Then in the .m file, after you instantiate the map view you should add:
mapView.delegate = self;//were self refers to your controller
From this point and on your controller will be able to "receive messages" from the map view which are the methods that you can see on the MKMapViewDelegate reference I linked to.
So to implement the mapView:didSelectAnnotationView you need to add in your .m file
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
//if you did all the steps this methosd will be called when a user taps the annotation on the map.
}
What is happening
What happens in the background is:
The map view has a method (Apple codded) that handles the AnnotationView touch events.
When a touch event take place it sends a "message" to all of it's delegates saying "Hey a user did Select Annotation View on this map, Do with it what ever you need".
usually it looks like that:
[self.delegate mapView:someMapView didSelectAnnotationView:someAnnotationView];
Then every view/controller that assigned itself as a delegate and implemented the method will cal this method.
Good luck
Place *place = [[Place alloc] init];
PlaceMark *placeMark = [[PlaceMark alloc] initWithPlace:place];
[self.mapView selectAnnotation:placeMark animated:YES];
I observed that viewDidLoad() is called before didFinishLaunchingWithOptions() and I am looking for something where I can put some initialization code that has to be called before viewDidLoad().
Is there such a place?
Also, it is acceptable to recall viewDidLoad() from other place. It should be ok, or too risky?
You are wrong.
Place a NSLog directly under the method header and you will see that ViewDidLoad is directly called after.
[self.window addSubview:self.yourViewController.view];
So, you either use viewDidLoad or alternatively and not really beautiful you could use.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
This even gets called before ViewDidload
There's
- loadView()
Kicks in before viewDidLoad() and comes with an advice to never be called directly after that.
Here's the link to the apple docs.
I had a similar problem once that was caused when I added the view controller I wanted added to the window using the MainWindow.xib file.
To get around this, I assigned the window's rootViewController (you can also call addSubView, but assigning the rootViewController is better) in the didFinishLaunchingWithOptions: method of the app delegate. Once you do this you can easily put whatever logic you want in front or behind where this happens. It puts you in full control of when your view controller loads. In contrast, when the view controller is loaded via the nib, it's difficult to execute code in front of it (if at all possible). I know you sepecify the main xib in the app's plist, but I don't know if there is a way to run code before that nib is loaded.
In general I avoid adding the view controller in the xib for this reason.
My code looks more like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// special pre load logic here...
UIViewController *myVC = [[MyAwesomeViewController alloc] init];
self.window.rootViewController = myVC;
[myVC release];
// special post load logic here...
[self.window makeKeyAndVisible];
return YES;
}
You may do your app initializations in viewDidLoad, however don't do any boundaries or size settings here, as they are not yet set.
Do them in viewDidLayoutSubviews, which is called after viewDidLoad.
I found this article, which was very helpful to me:
The UIViewController lifecycle
You could put initialization code in the init method of the class.
And it's fine to call viewDidLoad again from elsewhere. It's just like any other method.
EDIT:
It's fine to call viewDidLoad - but you should be careful with memory management. If you're allocating objects in viewDidLoad, calling it again will cause leaks. So, because of the typical functionality of viewDidLoad, you might want to pull the code out into another method that you'll call repeatedly and call from viewDidLoad.
I was just curious weather it is a good idea to call viewdidload a method like in an ibaction or something of that kind.
thanks,
TC
Check:
UIViewController Class Reference
viewDidLoad
This method is called after the view
controller has loaded its associated
views into memory. This method is
called regardless of whether the views
were stored in a nib file or created
programmatically in the loadView
method. This method is most commonly
used to perform additional
initialization steps on views that are
loaded from nib files.
The viewDidLoad method is automatically triggered.
Generally there is no need to trigger viewDidLoad yourself.
If you need to run specific code both after loading and button-click, do this:
- (void)viewDidLoad {
[self specificFunction];
}
- (IBAction)theButton:(id)sender {
[self specificFunction];
}
- (void)specificFunction {
// This code wil run after the view has been loaded
// and when the user clicks the button
}
The disadvantage of calling ViewDidLoad is , the superclass methods would be called as it has [super viewDidLoad], which is not a good idea .
It's better to have a separate methods and call them whenever necessary .
I would like to update the UI in my application from the AppDelegate, but whenever I call it as so:
Controller *object = [[Controller alloc] init];
[object methodHere];
It doesn't seem to update the UI. What am I doing wrong here? I have put in a NSLog to see if it was being called, and it is. Here is a sample project that shows the error.
Edit: Can someone just show me what to change to the project I provided. I just don't know what to type into my project so that I can change the value of a simple NSTextField from another class.
When you write [[Controller alloc] init], you are not accessing the Controller object that is in your nib. You are creating a new Controller object that is unconnected to anything else in your application.
Remember, every Controller object is not the same any more than every NSArray is the same. Just because you made one Controller in your nib that's connected to an NSTextField does not mean some random Controller that you just created shares that controller's connections.
What you need to do is give the delegate a reference to the Controller that's in the nib.
This is really simple, and Chuck's comments basically explain what you need to do, but I will lay out the code explicitly for you. In testAppDelegate.h:
#interface testAppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
// You can make an IBOutlet to any kind of object you
// want; it's just a way for you to get a reference
// in code to an object that has been alloc'd and
// init'd already by the xib mechanism.
IBOutlet Controller *controller;
}
Then go into your xib in InterfaceBuilder and hook up that outlet from your Test App Delegate object to your Controller object (these objects are already present in the xib).
In testAppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// This is the key:
// _Don't_ alloc/init a new controller object. The
// objects in your xib are allocated and initialized
// by virtue of being in that file. You just need to
// give your AppDelegate a pointer to it, as above.
[controller setTextValue:#"hello"];
}
It's being called all right, but it's not connected to the interface. There should be a view controller of some sort defined in your appDelegate.h file, call the method on that object instead.
Update for more detail:
One way you could pull this off would be to simply save the Controller when you originally create it (and not release it until later.)
Simply put your own controller object into your .h file
Controller* myController;
And when you create the new view controller you want to flip to, simply set myController to reference that object, and later when you want to update the UI, simply call
[myController methodHere];
A bit clumsy, but it works. Just don't forget to release myController when you're done with that view.
The other idea I'd suggest looking into would be to alter the method you're passing to your delegate. That is, instead of having the method as
-(returnType)callDelegateToDoSomething;
put it in as
-(returnType)callDelegateToDoSomething:(id) sender;
You call the new method the same way, but your controller should automatically pass itself as an argument. Then, inside the method, simply use
[sender methodHere];
and it should hopefully work. (You may need to play around with it a little. I'm not an expert on delegates or the sender argument, but it's worth a shot.)