Disclaimer: The program I wrote works. I just need help understanding it.
Today I just started learning XCode and Objective-C. I have tons of experience with Java and I must admit this is very different. I'm currently following a book that has us dealing with two scenes in our story board. The Main View Controller Scene and the Flipside View Controller Scene.
In the main scene I have one label outlet that says Hello World. It's name is label. In the flipside scene I have a text outlet. When the user flips from the flipside scene to the main scene the text in the text outlet is applied to the label outlet. So if I type in Hello StackOverflow in the flipside, and then flip it I will see Hello StackOverflow in the main scene.
We did this by going to the method that controls the flip and is in the main scene .m class and added this code.
self.label.text = controller.labelText.text;
Can anyone explain this code please? I understand that label and labelText are my names. And text is looking for the text. But I have no idea where self and controller came from and it is not explained in the book. Thank you.
EDIT
Here is the full code with the function that has controller in the function heading. I don't get what is going on in this method. Any explanation would be great.
- (void)flipsideViewControllerDidFinish:(HWFlipsideViewController *)controller
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
[self dismissViewControllerAnimated:YES completion:nil];
}
else
{
[self.flipsidePopoverController dismissPopoverAnimated:YES];
self.flipsidePopoverController = nil;
}
self.label.text = controller.labelText.text;
}
self is the java this pointer and therefore points to your object
text is a property and would be getText()
so self.label.text is this.getLabel().getText() BUT
as it sets text=something it is setText!
=> java-bean style getter and setter
controller is likely a local variable.. PASSED to the method as an argument
OR it is a member variable on the object of the this instance
so it'd be
this.getLabel().setText(controller.getLabelText().getText());
this = the object you're in and that the main view I think
controller is the flipview
the 'overwrite' is setting the String of our label to the Controller's textfield string
You described, that you are in the context, where the flip is controlled. So the instance of the second controller (named controller) must be available there. So you may refer to it.
self is, as Xono stated in the comment already the pointer to the current object like this in java.
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
Hi I have a mapView that has annotations pop up, I want to be able to segue when the annotation callout button is clicked. I have some problems though when I do it. I have a few questions
1) Do I have to embed the mapViewController in a navigation Controller? If yes, my annotations do not show up when I do, how come?
2) does prepareforsegue get called from performSegueWithIdentifier?
3) when u send self, in this case what would self be?
Thanks
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"Present Photo" sender:self];
}
Realized the problem it occurs here, I used to get a map controller from the id detail but now I think its a navigation controller, how do I get reference to the map controller now?
-(void) updateSplitViewDetail{
// ERROR OCCURS HERE!!! No longer map controller since I embed in navigation controller
id detail = [self.splitViewController.viewControllers lastObject];
if ([detail isKindOfClass:[MapViewController class]]) {
MapViewController *mapVC = (MapViewController*) detail;
mapVC.delegate = self;
mapVC.annotations = [self mapAnnotations];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self updateSplitViewDetail]; //Error may be here
}
1)
Yes. If you want to perform a push segue, the source view controller (your map view controller) should be embedded in in navigation controller.
I'm not sure why your annotations/callouts aren't appearing in that case -- I've seen plenty of projects that work correctly that way. Perhaps your reference to the map view when you add the annotations isn't what you think it is? (And you're adding annotations to nil instead?) You'll need to provide more details for us to help. (Edit your question or post a new question since it's sort of a separate issue.)
2)
Yes. prepareForSegue:sender: is called after you call performSegueWithIdentifier:sender:.
3)
The "sender" argument in these methods is entirely for your own use -- its sole reason for existence is to allow you to pass some context from the code that calls performSegueWithIdentifier:sender: to the implementation of prepareForSegue:sender:. (Or in the case of segues automatically performed when the user taps some control, to allow your prepareForSegue:sender: implementation to know which control was tapped.)
So, pass whatever you want: self is fine, and so is nil if you're not making use of it. Or if it's useful for your prepareForSegue:sender implementation to know which callout was tapped, you might consider passing the annotation view's annotation as "sender" (say, so it can set up the destination view controller with appropriate info).
I can't seem to figure this out for the life of me. I have a custom table view cell, in that cell I have a few buttons configured. Each button connects to other view controllers via a storyboard segue. I've recently removed these segues and put a pushViewController method in place. Transition back and forth across the various views works as expected however the destination view controller is not displaying anything! I have some code below as an example.
Buttons have this method set:
[cell.spotButton1 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
[cell.spotButton4 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
showSpotDetails Method contains this code:
- (void)showSpotDetails:(id)sender
{
// determine which button (spot) was selected, then use its tag parameter to determine the spot.
UIButton *selectedButton = (UIButton *)sender;
Spot *spot = (Spot *)[spotsArray_ objectAtIndex:selectedButton.tag];
SpotDetails *spotDetails = [[SpotDetails alloc] init];
[spotDetails setSpotDetailsObject:spot];
[self.navigationController pushViewController:spotDetails animated:YES];
}
The details VC does receive the object data.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"spotDetailsObject %#", spotDetailsObject_.name);
}
The NSLog method below does output the passed object. Also, everything in the details view controller is as it was. Nothing has changed on the details VC. It just does not render anything ever since I removed the segue and added the pushViewController method. Perhaps I am missing something on the pushViewController method? I never really do things this way, I try to always use segues...
Any suggestions?
Welcome to the real world. Previously, the storyboard was a crutch; you were hiding from yourself the true facts about how view controllers work. Now you are trying to throw away that crutch. Good! But now you must learn to walk. :) The key here is this line:
SpotDetails *spotDetails = [[SpotDetails alloc] init];
SpotDetails is a UIViewController subclass. You are not doing anything here that would cause this UIViewController to have a view. Thus you are ending up a with blank generic view! If you want a UIViewController to have a view, you need to give it a view somehow. For example, you could draw the view in a nib called SpotDetails.xib where the File's Owner is an SpotDetails instance. Or you could construct the view's contents in code in your override of viewDidLoad. The details are in the UIViewController documentation, or, even better, read my book which tells you all about how a view controller gets its view:
http://www.apeth.com/iOSBook/ch19.html
The reason this problem didn't arise before is that you drew the view in the same nib as the view controller (i.e. the storyboard file). But when you alloc-init a SpotDetails, that is not the same instance as the one in the storyboard file, so you don't get that view. Thus, one solution could be to load the storyboard and fetch that SpotDetails instance, the one in the storyboard (by calling instantiateViewControllerWithIdentifier:). I explain how to do that here:
http://www.apeth.com/iOSBook/ch19.html#SECsivc
Basically I want to use a nib file and view controller as a template for a view that I plan to create a number of times. This nib will have a couple of labels and custom views.
The idea is that I will iterate through an array of objects and for each I will create an instance of this controller and set a property to that of the object from the array.
This all works nicely at the moment except for one thing - the labels won't update when I call setStringValue: !!!
I'm using a method within the view controller's code to make the change but it just doesn't work, I'm guessing that the IBOutlet isn't being hooked up properly, which is strange because the custom views are hooking up perfectly.
Any ideas?
Set a breakpoint on awakeFromNib and look in the debugger what the value of the label outlet is. All outlets should have been connected before awakeFromNib is being called. If it is still nil, you have your answer. Calling setStringValue: on nil does exactly "nothing". In that case you have not correctly bound the outlet or maybe you once had it bound correctly and later on changed the name, in that case there should be a yellow warning triangle in Xcode4 or interface builder indicating that something is wrong; however it will not prevent your app from building or running, the outlet will simply keep its initial value after object creation (which is nil).
When you alloc your NSViewControllers, just init with name of NIB:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
Thanks for the replies, they were helpful but not quite what I was getting at.
I ended up solving it by creating an empty NIB and filling it with just a custom NSView and a few other controls. I created an NSView subclass with IBOutlets for those controls and set the custom view's identity to my subclass in interface builder.
The trick in getting it to work each time I wanted to draw it was by making a class method in my subclass that would load the nib and return the view set up the way I wanted.
Code below:
+(id)todoViewFromNibWithFrame:(NSRect)frameRect todoList:(TodoList *)aTodoList
{
NSNib *todoViewNib = [[NSNib alloc] initWithNibNamed:#"TodoView" bundle:nil];
NSArray *objects = nil;
id todoView = nil;
[todoViewNib instantiateNibWithOwner:nil topLevelObjects:&objects];
for (id object in objects) {
if ([object isKindOfClass:[self class]]) {
todoView = object;
[todoView setTodoList:aTodoList];
break;
}
}
[todoViewNib release];
return todoView;
}
Thanks again for the replies!
Steve
Initially I created a simple program with a custom NSView. I drew a picture (certificate) and printed it! beautiful! Everything worked great!
I then moved my custom NSView to an existing application. My hope was that when a user hit print it would print this certificate. Simple enough. I figured a could have a NSView pointer in my controller code. Then at initialization I would populate the pointer. Then when someone wanted to print the certificate it would print. The problem is that all of my drawing code is in the "drawRect" method. This method doesn't get called because this view is never displayed in a window.
I have heard that others use non-visible NSView objects just for printing. What do I need to do? I really don't want to show this view to the screen.
Rodger
You don't have to create the view in advance, you can create it when needed.
If you have Document Based application and a view that you want to dump to printer, then in our MyDocument (or whatever you call it) that extends NSDocument you would implement:
- (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)ps
error:(NSError **)e
The view then uses standard drawRect: for drawing.
Example, here PeopleView just draws a table of people details, it takes a NSDictonary of people employees here:
- (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)ps
error:(NSError **)e
{
PeopleView * view = [[PeopleView alloc] initWithPeople:employees];
NSPrintInfo * printInfo = [self printInfo];
NSPrintOperation * printOp
= [NSPrintOperation printOperationWithView:view
printInfo:printInfo];
[view release];
return printOp;
}
You can look for more details in chapter 27, Printing, in Hillegass' Cocoa Programming For Mac OS X.