I have a custom class myCustomClass which is a subclass of UITextField. (I'm going to call myCustomClass in a viewControllers class.)
In myCustomClass, I'm trying to check what kind of viewController the class that called it is. (UIViewController, UITableViewController etc.)
I tried:
if ([self.superview.nextResponder isKindOfClass[UIViewController class]]) {
NSLog(#"View Controller");
} else if ([self.superview.nextResponder isKindOfClass[UITableViewController class]) {
NSLog(#"TableView Controller");
}
I only get a result if the superclass is a viewController. So I did the following:
NSLog(#"%#", self.superview.nextResponder);
Results
UIViewController Class - ViewController
UITableViewController Class - UITableViewCell
How can I check if it's a UITableViewController?
For your specific case, you can use [self.superView isMemberOfClass:[UITableViewCell class]] to check if your custom view is inside a table view cell, which (unless you are using tableViewCell in an unusual way!) means that it's being called from a UITableViewController.
More generally, if you wanted to find out the containing view controller, you can recursively walk up the responder chain to find the containing viewController as in the second answer in this post:
Get to UIViewController from UIView?
It's also important to note that there is a difference between isKindOfClass: and isMemberOfClass:
isKindOfClass returns YES if 'the receiver is an instance of given class or an instance of any class that inherits from that class.'
isMemberOfClass returns YES if ' the receiver is an instance of a given class.'
Therefore, your UITableViewController, which inherits from UIViewController, will answer YES to isKindOfClass:[UIViewController class], will your if statement to act unexpectedly. (Though in the example it also didn't work correctly because you still needed to walk up the responder chain further).
So, if you in fact are comparing a UIViewController to a UITableViewController use -isMemberOfClass and your logic in the example would work as expected.
Related
I have a UITableView comprised of custom UITableViewCells. In each cell, there is a UILabel and a UISlider. Does anyone know how to, upon a change in value of one of the sliders, send the new value of the slider from the custom UITableViewCell (in a separate file) to the UITableViewController, so that I can then update the array from which the table was populated?
The closest I've got so far is a failed hack: firing a setSelected event when a slider value is changed. Whilst this highlights the changed custom cell, the event is not picked up by didSelectRowAtIndexPath in the UITableViewController.
Whilst code is always appreciated, a conceptual/method solution is what I am looking for.
Thank you in advance,
Jamie
What you need is called Delegate Pattern.
Quoting from there to explain what does it mean:
Delegation is a simple and powerful pattern in which one object in a
program acts on behalf of, or in coordination with, another object.
The delegating object keeps a reference to the other object—the
delegate—and at the appropriate time sends a message to it. The
message informs the delegate of an event that the delegating object is
about to handle or has just handled. The delegate may respond to the
message by updating the appearance or state of itself or other objects
in the application, and in some cases it can return a value that
affects how an impending event is handled. The main value of
delegation is that it allows you to easily customize the behavior of
several objects in one central object.
These diagrams will help you understand what goes on:
Architecture:
Operation:
Now as to how to implement it, this is what you have to do.
For Objective-C:
First of all, create delegate methods of your UITableViewCell. Lets name it ContactTableViewCell.
In your ContactTableViewCell.h file, do this:
#protocol ContactCellDelegate <NSObject>
#required
-(void) didMoveSliderWithValue:(float) value;
#end
#interface ContactTableViewCell : UITableViewCell
#property (weak, nonatomic) id<ContactCellDelegate> delegate;
Now conform your TableViewController to this delegate. Let's name your VC MyTableViewController.
In MyTableViewController.h, Do this:
#interface MyTableViewController : UIViewController <ContactCellDelegate> //Use UITableViewController if you are using that instead of UIViewController.
In your cellForRowAtIndexPath, before returning cell, add this line:
cell.delegate = self;
Add implementation for the delegate method INSIDE your MyTableViewController.m.
-(void) didMoveSliderWithValue: (float) value
{
NSLog(#"Value is : %f",value);
//Do whatever you need to do with the value after receiving it in your VC
}
Now let's get back to your ContactTableViewCell.m. In that file you must have added some IBAction to capture the value change event in slider. Let's say it is the following:
- (IBAction)sliderValueChanged:(UISlider *)sender {
self.myTextLabel.text = [#((int)sender.value) stringValue]; //Do whatever you need to do in cell.
//Now call delegate method which will send value to your view controller:
[delegate didMoveSliderWithValue:sender.value];
}
When you call delegate method, it will run the implementation that we wrote earlier in the MyTableViewController. Do whatever you need in that method.
What happens here is that your Cell sends the message to your desired VC (Which is delegate of the Cell), that "Hey, Call the delegate method that we wrote earlier in your body. I am sending you parameters right away". Your VC takes the parameters and does whatever you wanted it to do with that info and at that time.
For Swift:
First of all, your TableViewCell.swift file, create a protocol like this:
#class_protocol protocol ContactCellDelegate {
func didMoveSliderWithValue(value: Float)
}
Now in your Cell class, create a delegate property like:
var cellDelegate: ContactCellDelegate?
In your Slider IBAction, call the delegate method like this:
self.cellDelegate?.didMoveSliderWithValue(slider.value)
In your VC do these changes:
Make it conform to the delegate:
class MyTableViewController: UIViewController, ContactCellDelegate
Add this line before returning cell in cellForRowAtIndexPath
cell.cellDelegate = self //Dont forget to make it conform to the delegate method
Add the implementation of required delegate method:
func didMoveSliderWithValue(value:float) {
//do what you want
}
I have kept the Swift part precise and summarized because It should be very easy to change the detailed Obj-C explanation to Swift implementation. However If you are confused about any of the pointers above, leave a comment.
Also see: StackOverflow answer on using Delegate pattern to pass data back
I am trying to do some debugging on a project I am working on, and would like to know the kind of ViewController a specific variable is being assigned. So I created an if statement like so:
if ([controller isSubclassOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
'controller' is created just above using
DetailViewController *controller = (DetailViewController *)[[segue destinationViewController]topViewController];
DetailViewController is a simple class inheriting from UIViewController. However I'm getting a compiler error saying No visibile #interface for 'DetailViewController' declares the selector 'isSubclassOfClass:'
How is that possible? When I tripleTap the reference for isSubclassOfClass it says it's a class method defined in NSObject. How is it possible that DetailViewController doesn't know that selector since all objects inherit from NSObject??
Both other responders gave you a correct answer to your question, but I wanted to clarify something.
Methods who's declaration start with a + are CLASS methods. The class object implements those methods, not instances of that class. So this method:
+ (BOOL)isSubclassOfClass:(Class)aClass
Is a class method.
You'd use it like Jeffery Thomas demonstrated in the first part of his answer:
[[controller class] isSubclassOfClass:[UINavigationController class]]
The [controller class] bit fetches the class object for the controller object, and then sends that class object the isSubclassOfClass message.
In contrast, the method isKindOfClass:
- (BOOL)isKindOfClass:(Class)aClass
...is an instance method. You can tell because it's declaration starts with a "-" instead of a "+". Learn to look for the "+" or "-" at the beginning of every method declaration to see if it's a class method or an instance method. And if the compiler isn't letting you send a message that you see in the docs, go back and double-check. I've been programming in Objective-C for quite a few years now and this still bites me occasionally.
You send the isKindOfClass message to an instance of the object you want to test for class membership:
[controller isKindOfClass:[UINavigationController class]]
The standard way of doing this is -isKindOfClass:, not +isSubclassOfClass:, but it's possible to use +isSubclassOfClass:.
Using the instance method -isKindOfClass:
if ([controller isKindOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
Using the class method +isSubclassOfClass:
if ([[controller class] isSubclassOfClass:[UINavigationController class]]) {
NSLog(#"It's a navigation controller!");
}
When I tripleTap the reference for isSubclassOfClass it says it's a class method defined in NSObject.
It's a class method, and you're using it on an instance. Try isKindOfClass.
I am making master detail application, i have dynamic Detail ViewController. Detail ViewController are changed.
But in every Detail ViewController I have one common method updateInfo I want to call that method
Here is my code
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
UIViewController *controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But it gives me error no method found.
it will work if i use UIViewController name.
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
But i dnt want to do above things.
I have tried to explain. Please help
You can use id
UINavigationController *nav=[self.splitViewController.viewControllers objectAtIndex:1];
id controller=[nav.viewControllers objectAtIndex:0];
[controller updateLastInfo];
You could subclass UIViewController and make a base DetailViewController class that houses common functionality of your detail view controllers. Then you would make all of your detail view controllers subclass DetailViewController instead of UIViewController. This would be a safe way to do it and would also allow you to add extra functionality to your updateInfo method in the specific detail view controllers.
If you want an unsafe way, you could make your controller object of type id. I wouldn't suggest this approach as it relies on your personal knowledge of the code. If someone else (or yourself down the road) sets it to a view controller that doesn't have that method, the code will still try to run and will crash.
UIViewController doesn't have a method named updateInfo, so the compiler will of course complain when you try to send that message to a pointer that's known only to point to an instance of UIViewController. When you use the class name, like this:
HomeViewController *controller=(HomeViewController)[nav.viewControllers objectAtIndex:0];
you're providing more information to the compiler, using a type cast to tell it "Hey, don't worry, I know for certain that the object I'll get back is a HomeViewController. Since you seem to have several types of view controllers that all have this method, the best thing to do is to declare the updateInfo method in a protocol and then have each of those UIViewController subclasses implement that protocol. So, your protocol declaration would be in a header file and might look like:
#protocol SomeProtocol
- (void)updateInfo
#end
and each class that has an -updateInfo method would just need to declare that it adopts the protocol:
#interface HomeViewController <SomeProtocol>
//...
#end
and then make sure that you have an -updateInfo in your class implementation:
#implementation HomeViewController
- (void)updateInfo {
//...
}
//...
#end
Then, in your code, you can either check that the object conforms to the protocol using -conformsToProtocol: like this:
if ([controller conformsToProtocol:#protocol(SomeProtocol)]) {
UIViewController<SomeProtocol> *c = (UIViewController<SomeProtocol>*)controller;
[c updateInfo];
}
or else just check that the object responds to the selector before calling it:
if ([controller respondsToSelector:#selector(updateInfo)]) {
[controller performSelector(updateInfo)];
}
The other answers you've received (using id or creating a common base class) are also good ones, but to be safe make sure you do some checking before calling your method. For example, you can use -isKindOfClass to make sure that the view controller you get back is in fact an instance of your common base class, and you can use -respondsToSelector: as above to check that an id points to an object that implements updateInfo.
Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}
I have a viewcontroller that can show several popovers. Not at the same time. Which would be the best way to know which popover is being dismissed at popoverControllerDidDismissPopover?
I have to do different actions regarding the popover that is being dismissed.
Thanks a lot
Something like this should work. (This code is not complete - I assume you know basic memory and class management and other stuff so I focus on the actual problem)
In your class keep some ivars to store reference to the popovercontrollers you created
#interface MyClass : NSObject <UIPopoverControllerDelegate> {
UIPopoverController *popover1;
UIPopoverComtroller *popover2;
}
Init your popovercontrollers as usual and save a referance to each of them in popover1 and popover2.
Then in your implementation of the UIPopoverDelegate protocol:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if(popoverController == popover1) {
//popover1 was dismissed
} else if (popoverController == popover2) {
//popover2 was dismissed
}
}
EDIT: Looking at your comments, it seems that you mean that you are using only ONE popovercontroller, and replacing it's content view with different UIViewControllers.
If this is the case, I suggest you perform whatever the actions are inside those particular UIViewController in such a way that it affects your application´s state.
Then, once the popover is dismissed, you reload your original view with the new refreshed state.
Or you change it to use two different instances of UIPopoverController instead.
This is how in Swift as of Xcode 6.3 beta 3, should be similar in Objective-C.
Your presented view should have a ViewController for itself.
import UIKit
class MenuBookmarksViewController: UITableViewController {
}
Add an extension to the UIViewController class or place the code (below) inside the UIViewController that will be presenting your popovers:
extension UIViewController: UIPopoverPresentationControllerDelegate {
public func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
if popoverPresentationController.presentedViewController as? MenuBookmarksViewController != nil {
///do your stuff
}
}
}
You are passed which popover is being dismissed in popoverControllerDidDismissPopover:. Use that to determine what you want to do in each case.
You'll probably want to store your UIPopoverController instances as ivars of whatever object is presenting them, and then just compare against the value that you're passed in the delegate method.