I have a viewcontroller A controlling a large view. On that view I have a subview C of type CollectionView.
I want a separate class C_Manager to handle the delegate and dataSource functionality for the C subview
I have the following
in A's viewDidLoad
cManager = [[C_Manager alloc] init];
cManager.collection = C;
self.C.delegate = cManager;
self.C.dataSource = cManager;
in C_Manager.h I have
#interface C_Manager : NSObject <UICollectionViewDataSource,UICollectionViewDelegate>
and in C_Manager.m I have :
// all the functions for the dataSource plus
-(void)collectionView:(UICollectionView*)collectionview didSelectItemAtIndexPath:(NSIndexPath*) indexPath
{
}
I never reach the didSelectItemAtIndexPath. Interestingly, I do reach the functions for the dataSource protocol, and if I disable the line which sets it - I dont reach it anymore...
I checked the view C in the xib - the "User Interraction Enabled" is checked ...
What is wrong ?
Related
I have 1 view controller where depending on the button that is clicked, a view at the bottom is swapped between 4 different table views. These table views are in their own separate UITableViewController's in the storyboard. I add the tableViews like this:
Tracks_TVC *tracksTVC = [[Tracks_TVC alloc] init];
tracksTVC.view.frame = _postView.frame;
tracksTVC.view.tag = kTagPostView;
[self.view addSubview:tracksTVC.view];
I get this error message when the code is run:
NSInternalInconsistencyException', reason: 'UITableView dataSource
must return a cell from tableView:cellForRowAtIndexPath:
I've implemented the data source in the separate UITableViewController's properly (I believe so) with all the required methods so I'm confused as to why I'm receiving this error. My only thought is that the added table view isn't using the methods in it's own UITableViewController..? Any help on this matter would be greatly appreciated!
You should create the property of TableViewController such as -
#property(nonatomic, strong) Tracks_TVC *tracksTVC;
Now in viewDidLoad initialise the same -
-(void)viewDidLoad
{
[super viewDidLoad];
self.tracksTVC = [[Tracks_TVC alloc] init];
self.tracksTVC.view.frame = _postView.frame;
self.tracksTVC.view.tag = kTagPostView;
[self.view addSubview:self.tracksTVC.view];
}
Note : Also make sure that you are returning the cell in Data source.
Screencast: https://www.youtube.com/watch?v=ZwehDwITEyI
This is really bizarre. The problem is to do with a label outlet sitting in a custom-designed table cell. That cell is of my CustomCell class. (actually called RA_FormCell if you watch the screencast).
CustomCell.h
#property (weak, nonatomic) IBOutlet UILabel *dayOutlet;
-(void)controller:(id<CustomCellDelegate>)controller didUpdateDay:(NSString *)theDay;
CustomCell.m
// Method is called by a view controller
// (which is itself a delegate of the CustomCell class,
// hence the identifier you see below)
-(void)controller:(id<CustomCellDelegate>)controller didUpdateDay:(NSString *)theDay;
{
NSLog(#"Method called") // confirms to me that method is called
self.dayOutlet.text = #"Goodmorning";
NSLog(#"%#", self.dayOutlet.text); // displays (null)
}
That final log does actually appear, so the method is definitely being called. I have discounted the following:
self.dayOutlet.text is not written to elsewhere by any other method in the project
dayOutlet is connected to the label in the storyboard (and the label is not connected to anything else)
The label is not hidden underneath some accidental static label on the storyboard
The cell attributes on the storyboard include its class as CustomCell
No warnings or alerts in Xcode (I have been careful to avoid any circular imports)
The problem was that the controller:didUpdateDay: message was not sent to the correct instance of the cell class.
This occurred because I had not correctly assigned this cell to be the delegate for the view controller. For anyone interested, in my screencast at 3:50, you can see that I have the following in cellForRowAtIndexPath:
RA_FormCell *cell = [tableView dequeueReusableCellWithIdentifier:self.formCellArray[indexPath.row] forIndexPath:indexPath];
self.delegate = cell
However, this means that self.delegate got continually overwritten as the table cells were generated. As a result, my controller:didUpdateDay message was sent to the bottom cell of the table, and not the top one as I required.
The solution was simple - there's no need to have this second delegate at all. Instead, when the cell delegates to the view controller, it should pass self into the message it delegates:
id<CustomCellDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:#selector(customCell:didChangeDay1:)])
[strongDelegate customCell:self didChangeDay1:[sender value]];
Then, in the implementation of this method by the delegate, simply end it by changing the outlet directly:
-(void)customCell:(RA_FormCell *)customCell didChangeDay1:(double)value
// put logic here
customCell.dayOutlet.text = #"No problem!";
In general, there should rarely be a need for a two-way delegate structure. Keep it one way, from A to B, and just remember to have A pass self in any messages it sends to B. That way, B will know the object the message came from, and be able to communicate back to A.
Thanks to Paulw11
This may sound silly, but read on...
I want to set the text of a UILabel from outside of a UIViewController that is instantiated by a storyboard. I need to make sure that the label property of the view controller is set when I set its text otherwise the label's text won't be set(because it won't be loaded yet to receive a text value).
Here's my current solution:
// Show pin entry
if (!self.pinViewController) {
// Load pin view controller
self.pinViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"pinScreen"];
self.pinViewController.delegate = self;
if (!self.pinViewController.view) {
// Wait for pin screen to fully load
}
[self.pinViewController setMessageText:#"Set a pin for this device"];
}
Initially I had a while loop that looped until the value of view was not nil, But it seems the very act of checking the view loads it(as mentioned here: http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW37)
I tried using the isViewLoaded method with no success. It just looped forever.
I've gone forward with the above code as my current solution, but it feels wrong.
Is there a better way ensure a UIView has loaded?
I want to propose an alternative way where you don't have to rely on the availability of the view.
If you need to wait for the view to load before you can call other methods on your viewController you break encapsulation, because the viewController that calls your PinViewController has to know about the inner workings of your PinViewController. That's usually not a good idea.
But you could save objects like NSStrings in the PinViewController instance, and when the view of the PinViewController will appear you set its views according to the properties you have set before.
If you need to change the text of an label from outside your viewController you can also create a custom setter that sets the label.text for you.
Your .h
#interface PinViewController : UIViewController
#property (copy, nonatomic) NSString *messageText;
// ...
#end
And your .m
#implementation PinViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.messageLabel.text = self.messageText;
}
// optional, if you want to change the message text from another viewController:
- (void)setMessageText:(NSString *)messageText {
_messageText = messageText;
self.messageLabel.text = messageText;
}
// ...
#end
viewDidLoad should solve this I guess.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html
I would rather see you change your logic and do it the way that #MatthiasBauch shows in his answer. However, to answer your actual question, you can simply set a view property in order to force it to load:
self.pinViewController.view.hidden = NO;
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 custom UIView, MyView, with a couple of tableviews in it - tv1 and tv2 - both added programmatically.
I have a custom UIViewController, MyController, which creates an instance of MyView on loadView.
My goal is the following. myView should be the datasource and delegate for myView.tv1, since it doesn't touch my data model and is static.
myController should be the delegate of myView.tv2, since its content will depend on the datasource.
I have added the following to each header:
#interface MyController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#interface MyView : UIView <UITableViewDelegate, UITableViewDataSource>
and added to each the required delegate methods.
In MyView's init, I add that table views, and set the delegate of the first one to self:
tv1 = [[UITableView alloc] initWithFrame:frame1 style:UITableViewStyleGrouped];
tv1.delegate = self;
tv1.datasource = self;
tv2 = [[UITableView alloc] initWithFrame:frame2 style:UITableViewStyleGrouped];
And in my Controller I connect up the second tableview:
- (void)loadView{
self.view = [[MyView alloc] initWithFrame:myFrame];
[(MyView *)self.view tv2].delegate = self;
[(MyView *)self.view tv2].dataSource = self;
}
When I run this, I get a whole mess of runtime errors. Sometimes it complains about a paging control, other times NSCFArray, othertimes no text at all. It always crashes.
I have determined that it is setting the datasource that causes the problem, not the delegate. If I comment out the datasource line in laodView, it runs fine (but with no content of course).
What could cause my to experience a different runtime error each time, and where might I be going wrong?
myView should be the datasource and delegate for myView.tv1, since it
doesn't touch my data model and is static
That's not really MVC, right ?
I think you'll be able to track the problems faster (or easily avoid it) if you prepared separate combined delegate&datasource classes for each table. The table1 datasource might be an internal class of MyView.
It's critically important to keep the delegate/datasource references retained by the table owner as the table will not retain it itself (that might be not so important for the case when you set the delegate/datasource to self both for table1 (MyView) and table2 (MyController), but you need to keep it in mind if you create separate delegate&datasource classes). Anyway you'll need to retain the array you are using as a datasource.