How to deal with Objective-C errors in dynamic code blocks? - objective-c

I'm sure the answer to this question is embarrassingly basic, but I'm having trouble understanding how the real-time compiling / error-checking in XCode is supposed to work with the dynamic nature of Objective-C.
For example, I want to setEditing:YES for the tableView of whatever the topViewController is in my stack of view controllers. So I try this:
[self.navigationController.topViewController.tableView setEditing: YES animated: YES];
And XCode complains: Property 'tableView' not found on object of type 'UIViewController'.
Now, this code is in a UIViewController, but it would only be called when the topViewController is a UITableViewController, but obviously Xcode doesn't know that.
How do I fix this? Is this indicative of a bad coding practice on my part? I tried wrapping the line in a conditional to test that topViewController.tableView != nil, but Xcode then just bitches about the conditional line :)
EDIT: Thanks to answers by saadnib and Caleb below, this is what I have now:
if ([self.navigationController.topViewController isKindOfClass:([UITableViewController class])] ) {
UITableViewController *topController = (UITableViewController *)self.navigationController.topViewController;
[topController.tableView setEditing: YES animated: YES];
}

Actually you can access the property of topViewController by typecasting it. For example your topViewController name is "FirstViewController" then you can do this as
FirstViewController *fvc = (FirstViewController*)self.navigationController.topViewController;
[fvc.tableView setEditing: YES animated: YES];
i hope this will help you.

You get the error because self.navigationController.topViewController returns a pointer of type UIViewController*, and UIViewController doesn't have a tableView property. #saadnib's answer is correct: if you know that the pointer will always point to a certain UIViewController subclass, you can cast it to that type.
However, even though you "know" that the top view controller will always be a table view controller, you might want to check at run time that that's the case. You could use -isKindOfClass: to see if the controller is a subclass of UITableViewController. You'd still need the cast, of course, but it'd be a little safer.

Related

Method Swizzling - Please explain property mapping within this implementation

I was looking into an open source pull-to-refresh control and it swizzle lifecycle methods on a UIViewController category like so:
- (void)INBPullToRefreshView_viewWillAppear:(BOOL)animated
{
[self setClearNavigationBar:YES];
[self INBPullToRefreshView_viewWillAppear:animated];
UITableView *tableView = self.pullToRefresh.tableView;
tableView.contentOffset = tableView.contentOffset;
self.pullToRefresh.showPullToRefresh = YES;
}
I get that when viewWillAppear was called it mapped to the above method, and that calling [self INBPullToRefreshView_viewWillAppear:animated]; will map to the original viewWillAppear.
However, what does the following do?:
tableView.contentOffset = tableView.contentOffset;
Here's the github source for the control.
I would suspect the author is trying to use a side-effect of setContentOffset:, perhaps forcing a recalculation. But the author seems active on the project, so why not ask intmain in a github issue?
Of course the standard warnings that this kind of method swizzling is extremely dangerous and fragile apply.
I believe you're asking something unrelated to the swizzling itself?
Setting the contentOffset property will cause a scrollViewDidScroll: message sent to the delegate of your object. There's probably a cleaner way to accomplish that (or at least it should have a comment)

I haven't encountered this syntax in Objective C before

This is from a book on iphone game development.
[((GameState*)viewController.view) Update];
"viewController" is an instance of UIViewcontroller, "GameState" is a subclass of UIView, and "Update" is a method of "GameState". Can you please tell me what is happening. Does this syntax allow the viewController to use the methods of GameState? I apologize if this is a stupid question.
All that's doing is telling the compiler "hey, viewController.view is actually of type GameState*". It doesn't actually do anything to it though, just lets the compiler know so it won't warn about it.
Note that it's entirely legal to lie to the compiler like this, and it will believe you, and not check your work, so it's best to avoid casting if you can. If you cast it to something it isn't, it will crash if you try to use methods it doesn't have.
What's going on here is a C type cast: you are telling the compiler that you know that your viewController's view is of type GameState, and that you know that it's OK to invoke methods of GameState here, even though these methods are not part of the UIView's interface.
Means that viewController's view is casted to a GameState (subclass of UIView) and in this way the compiler does not complain that Update method is invoked.
This has the inconvenience of potentially generating a runtime error so to be safe I will enclose the previous statement in:
if ([viewController.view isKindOfClass:[GameState class]])

How can I get a reference to the current UINavigationController?

In Objective-C, what is the best way of getting a reference to the current UINavigationController? I would like to gain access to it from any class that may not have a reference to a UIController, delegate, or anything else.
Is there an existing way to get the current UINavigationController? Should I add something to my application delegate, and retrieve that?
As it turns out, you can get it, albeit with quite a bit of dot syntax, from any UIView/UIViewController descendant.
self.(view).window.rootViewController.navigationController;
If you are using an object that descends from any other class, the same is true, except you must go through UIApplication
[UIApplication sharedApplication].keyWindow.rootViewController.navigationController;
Of course, both of these break MVC in a big way, and it is definitely recommended that any logic that must touch the "default navigation controller" be added to the root view controller.
Swift 4:
guard let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController else { return }

NSTextField will not update

[self.change setStringValue:[NSString stringWithFormat:#"%i", changeValue]];
where change is an IBOutlet of type NSTextField. For whatever reason I can not get the text field to display anything I want. Every time I run, the field is "0". Any ideas on why it won't change?
Never update textfield or pop-button in init. You have to fill default values in awakeFromNib or some other delegates once class gets initialized.
Ok here is the embarrassing rookie mistake for others to learn from. I was calling my setstringvalue before the view was even loaded; in the didfinishlaunching method. Use the awakefromnib method to make sure you view is loaded. Via Apple:
Finally, after all the objects are fully initialized, each receives an awakeFromNib message.
Verify that the outlet is really linked with the NSTextField you have put in the xib file.

NSCFString or UIViewController?

I am using UIViewController (a subclass of course) with a text field which sends an action when the contents changed (to the contentsChanged: selector of the ViewController). It is done by sending contentsChanged: to file's owner in IB.
But when I test it, it says : "-[NSCFString contentsChanged:] : unrecognised selector sent to instance " and the instance pointer in hex.
I am guessing that for some reason the view controller gets moved to another pointer and a string gets allocated there, but I cannot figure why.
Any ideas ?
Sounds like a classic case. Read up on NSZombieEnabled for how to track this sort of problem down.
I have the exact same problem with a subclass of UIViewController and this piece of innocuous code:
- (void)viewDidLoad
{
NSLog(#"%# %s %#", [self class], _cmd, answerButton);
[self.answerButton addTarget:self
action:#selector(getAnswerToQuestion:)
forControlEvents:UIControlEventTouchUpInside];
}
Yes, answerButton is connected (it's an IBOutlet), yes, - (IBAction)getAnswerToQuestion:(id)sender; is a proper method, but no joy. When I commented out the viewDidLoad and made the connection in IB, it showed in the crash report that the failure happens on [UIControl sendAction:to:forEvent:] resulting in
objc_msgSend() selector name: performSelector:withObject:withObject:
I can't prove it, but I suspect there's a bug somewhere in the UIKit that translates the bindings and addTarget to a call to performSelector. I'm planning to upgrade to iOS 4.01 first to see if that won't solve the problem.
UPDATE:
I'm not sure anymore that my problem really is similar to Alexandre Cassagne's but in the interest of sharing information I will not delete it just yet. I solved my problem, as so often, when I started to make an example project in order to file a bug report. Yes, clicking made answerButton call getAnswerToQuestion: like a good little object and all was fine.
The difference between the subclassed UIViewController of the example project and that of my real project was that the first also functioned as the xib's File's Owner while the second was just one of several view controller. When I moved getAnswerToQuestion: to the File's Owner in my real project, clicking answerButton worked as expected. So, my hunch that the problem lay somewhere in the translation from binding to performSelector wasn't that far off: the problem lies in the Responder Chain. I would think that establishing the Action-Target link either programmatically or in IB would bypass the Responder Chain, but apparently not.
The problem now, of course, is that Alexandre states in his question that his contentsChanged: method already is part of the File's Owner, which makes my answer irrelevant to the question.
without looking at the code, it looks like you are calling contentsChanged: on the text field's text, instead of the UIViewController subclass.
you should consider using the UITextFieldDelegate protocol to get called back when the text of a UITextField changes. I have not looked, but this is the thing I would do off the top of my head.