I have setup a delegate method on Custom cell. let's say,
#protocol CheckDelegate <NSObject>
#required
- (void)checkForUpdate;
#end
#property (nonatomic, weak) id<CheckDelegate> delegateForChecker;
-- I will call above function somewhere in my cell ---
and I want to listen for any change in Custom Cell at Viewcontroller. How can I implement this using the delegate in Viewcontroller without using tableviewcontroller.
In ViewController I though about doing,
-(void) viewDidLoad{
CustomCell * cll = [[Customcell alloc]init]
cll.delegateForChecker = self;
}
-(void) checkDelegate{
NSLog(#"Something changed");
}
P.S. I do not want use NSNotification
I came up with different approach to pass the data from cell to another viewcontroller (not the tableviewcontroller where the cell is instantiated) using block. This may help someone who may find themselves stuck with this issue.
In custom cell, I created a block property called that would pass the property to the tableviewcontroller where the cell is instantied.
#property (copy, nonatomic) void (^didTouchButtonOnCell) (BOOL selectedButton);
In button touched in customCell I set the boolean property with Boolean Value.
self.didTouchButtOnCell(YES/NO);
In the tableViewcontroller I created another block that would pass data to the viewcontroller where I intended to pass the Boolean value.
In the TableViewController,
#property (copy, nonatomic) void (^passBooleanToViewController) (BOOL selectionFromButton)
In cellForRowAtIndexPath in TableViewController, I get the boolean property and pass it to ^passBooleanToViewController.
cell.didTouchButtonCell = ^(BOOL selection)
{
passBooleanToViewController(selection)
}
Finally, in ViewController I again get the Block property of TableViewcontroller that contained the Boolean value that was passed over to TableViewController from CustomCell
Viewcontroller.passBooleanToViewController = ^(Bool sel){
NSLog(#"%I here is the boolean value",sel);
}
** This is one of the ways I was able to fix the issue of passing value from custom cell to viewcontroller. It would be great if some other approaches can be shared too.
Related
I have toggle buttons in my tableview cells and I click them on for some cells but when I scroll down, those same buttons are selected for the bottom cells as well even though I didn't select them yet. I know this is happening because of the tableview reusing cells...is there any way I can fix this?
The cells are dynamic, not static.
what the tableview looks like
** EDIT: Also, lemme know if my logic seems alright: I tried creating a mutable array in my viewcontroller class and then setting all it's values to #"0". Then, in my tableviewcell's class, I set the value in the array to #"1" at the index of the current cell if I select the button, so then back in my viewcontroller class, I can tell if I have already selected a button at that cell or not. The only flaw is that I can't access the array in my tableviewcell's class, it is coming out at null...i guess that it because of the mvc pattern in objective c. Any advice?
EDIT
I am still unable to resolve my issue. Can someone please help me? I have been stuck on it for a while now!
I am trying to create a tableview where the cells have a check and cross button and when I click the check button, it should turn green, but the same button in other cells should remain gray, however, when I scroll down, some cells that I didn't select buttons in still turn green...because of cell recycling.
I am using delegates and protocols right now but it isn't working; perhaps I am using it wrong?
I am setting yesChecked value in IBaction functions in my cell class, and in my viewcontroller class, I am using that yesChecked value to see what color to give to the button based on whether it says "yes" or "no".
Kindly help! Thanks!
#protocol DetailsTableViewCellDelegate <NSObject>
- (void) customCell:(DetailsTableViewCell *)cell yesBtnPressed:(bool)yes;
#property (nonatomic, retain) NSString * yesChecked;
You'd have to select or deselect them in cellForRowAt. For example if your cell had a leftButton property and you had a model like this, you could do something like the following:
#interface Model : NSObject
#property (nonatomic, assign) BOOL selected;
#end
#protocol CustomCellDelegate <NSObject>
- (void)cellActionTapped:(UITableViewCell *)cell;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, assign) BOOL leftButtonSelected;
#property (weak, nonatomic, nullable) id<CustomCellDelegate> delegate;
#end
// ModelViewController.h
#interface ModelViewController : UIViewController<CustomCellDelegate>
#end
// ModelViewController.m
#interface ViewController () {
NSArray<Model*>* models;
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier"];
((CustomCell *)cell).delegate = self;
((CustomCell *)cell).leftButtonSelected = models[indexPath.row].selected;
return cell;
}
- (void)cellActionTapped:(UITableViewCell *)cell {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
// Update data source using (maybe) indexPath.row
}
I have toggle buttons in my tableview cells and I click them on for some cells but when I scroll down, those same buttons are selected for the bottom cells as well even though I didn't select them yet. I know this is happening because of the tableview reusing cells...is there any way I can fix this?
The cells are dynamic, not static.
what the tableview looks like
** EDIT: Also, lemme know if my logic seems alright: I tried creating a mutable array in my viewcontroller class and then setting all it's values to #"0". Then, in my tableviewcell's class, I set the value in the array to #"1" at the index of the current cell if I select the button, so then back in my viewcontroller class, I can tell if I have already selected a button at that cell or not. The only flaw is that I can't access the array in my tableviewcell's class, it is coming out at null...i guess that it because of the mvc pattern in objective c. Any advice?
EDIT
I am still unable to resolve my issue. Can someone please help me? I have been stuck on it for a while now!
I am trying to create a tableview where the cells have a check and cross button and when I click the check button, it should turn green, but the same button in other cells should remain gray, however, when I scroll down, some cells that I didn't select buttons in still turn green...because of cell recycling.
I am using delegates and protocols right now but it isn't working; perhaps I am using it wrong?
I am setting yesChecked value in IBaction functions in my cell class, and in my viewcontroller class, I am using that yesChecked value to see what color to give to the button based on whether it says "yes" or "no".
Kindly help! Thanks!
#protocol DetailsTableViewCellDelegate <NSObject>
- (void) customCell:(DetailsTableViewCell *)cell yesBtnPressed:(bool)yes;
#property (nonatomic, retain) NSString * yesChecked;
You'd have to select or deselect them in cellForRowAt. For example if your cell had a leftButton property and you had a model like this, you could do something like the following:
#interface Model : NSObject
#property (nonatomic, assign) BOOL selected;
#end
#protocol CustomCellDelegate <NSObject>
- (void)cellActionTapped:(UITableViewCell *)cell;
#end
#interface CustomCell : UITableViewCell
#property (nonatomic, assign) BOOL leftButtonSelected;
#property (weak, nonatomic, nullable) id<CustomCellDelegate> delegate;
#end
// ModelViewController.h
#interface ModelViewController : UIViewController<CustomCellDelegate>
#end
// ModelViewController.m
#interface ViewController () {
NSArray<Model*>* models;
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier"];
((CustomCell *)cell).delegate = self;
((CustomCell *)cell).leftButtonSelected = models[indexPath.row].selected;
return cell;
}
- (void)cellActionTapped:(UITableViewCell *)cell {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
// Update data source using (maybe) indexPath.row
}
I'm developing an iOS-App and therefore I use a UITableViewController. Within "cellForRowAtIndexPath" I use cells with reuse identifiers:
[[[UITableViewCell alloc] initWithStyle:UITableViewCellStyle1 reuseIdentifier:textFieldIdentifier];
The problem is that some cells have a dependecy on each other, e.g. if the user enters text in one cell another cell changes its value.
So what is the best way to safe a reference to the cell that has to be changed? The problem is, that if I safe the reference within "cellForRowAtIndexPath", during the callback for "textFieldDidChange" the reference might be broken, e.g. if the cell is not visible or another cell has the adress due to the reuse identifier?!
Don't try to save references to cached cells. Update whatever you need to display in the table's data source and then call reloadData. That way, the table takes care of refreshing visible cells and dealing with the cache...so you don't need to.
I would make an protocol for the cells
Example
#protocol MyProtocol <NSobject>
- (void) changeText:(NSString)theText;
#end
#interface TableViewCell1 : UITableViewCell
#property (nonatomic, weak) id<MyProtocol> delegate;
#end
#implementation TableViewCell1
//put this in the method where you get the value of the textfield
[self.delegate chageText:#"Hello"];
#end
#interface TableViewCell2 : UITableViewCell <MyProtocol>
#end
#implementation TableViewCell2
- (void) chageText:(NSString *)text {
self.textLabel.text = text;
}
#end
I notice that Apple has what seems to be duplicate variable names:
2 properties and two ivars. Why does Apple do this?
//.h file
#interface TypeSelectionViewController : UITableViewController {
#private
Recipe *recipe;
NSArray *recipeTypes;
}
#property (nonatomic, retain) Recipe *recipe;
#property (nonatomic, retain, readonly) NSArray *recipeTypes;
And they then update the recipe instance below. Why have two variable with the same name?
Will one affect the recipe variable of the parentViewController since that recipe variable was set when presenting this view controller the code was in from the parentViewController?
//.m file
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// If there was a previous selection, unset the accessory view for its cell.
NSManagedObject *currentType = recipe.type;
if (currentType != nil) {
NSInteger index = [recipeTypes indexOfObject:currentType];
NSIndexPath *selectionIndexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell *checkedCell = [tableView cellForRowAtIndexPath:selectionIndexPath];
checkedCell.accessoryType = UITableViewCellAccessoryNone;
}
// Set the checkmark accessory for the selected row.
[[tableView cellForRowAtIndexPath:indexPath] setAccessoryType:UITableViewCellAccessoryCheckmark];
// Update the type of the recipe instance
recipe.type = [recipeTypes objectAtIndex:indexPath.row];
// Deselect the row.
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
UPDATE 1
This code is from Apple's iPhoneCoreDataRecipes core data example:
First have a look at the RecipeViewController's didSelect delegate method, which will present the TypeSelectionViewController (child) view controller. Then have a look at that viewcontroller's didSelect delegate method where you will find the code implementation.
The reason I started looking at this is because I was interested how the parent's tableView cell got updated based on the selection in the ChildViewController in editing mode.
To see this for yourself, do the following:
Run the application
Select the Recipes tab
Click on a recipe - Chocolate Cake.
Click the edit button on the top right
Make note of the current category - should be on desert - then click on it.
Then you will be taken to the child view controller
Click on a different category, then click back and you will notice that the category button for that recipe has magically been updated. And I don't know how that's happening.
Does it have something to do with the private ivars and properties? which affects the parentViewController's cell?
My question i Guess is, how does selecting a category type in the child view controller's table affect the cell.text in the Parent View Controller's table? I can't see where the managedObjectcontext is saved in the child view controller for it to automatically update the parent View controller's cell text.
What you're seeing here is relatively old code, and there's not much need to do this anymore, thanks to Objective-C auto-synthesis.
Nowadays, when you issue a #property (nonatomic) NSArray *foo;, you implicitly get a #synthesize foo = _foo; in your implementation file and an instance variable declaration in your header. You don't see this, the compiler "inserts" it automatically. foo is the property and _foo is the instance variable. (In your above example, the #property and backing instance variable are both the same name, which could get confusing very quickly. With the foo property, you couldn't accidentally say self._foo, that doesn't exist. There's self.foo and _foo. With your example recipe is the ivar and self.recipe is the property. Very easy for one to quickly confuse the two when reading code.
Before the auto-synthesis, there was an intermediate step where you still needed a #synthesize, but you the backing instance variable was generated for you. These new features help you remove boilerplate code.
Answering Update 1
The code doing what you're wondering is in tableView:cellForRowAtIndexPath. There's nothing magical here. When you selected a new Category via the TypeSelectionViewController, the NSManagedObject is updated. Back in the RecipeDetailViewController, cellForRowAtIndexPath pulls the lasted information from CoreData. text = [recipe.type valueForKey:#"name"];
You might be getting confused about what an #property really is. It's just syntactic sugar. A #property these days automatically creates accessor and mutator methods and a backing ivar. Properties themselves aren't areas to store data, it's just a quick way of generating some methods and backing ivars.
Example
#interface MyClass
{
NSUInteger _foo;
}
#end
#implementation MyClass
- (NSUInteger)foo
{
return (_foo)
}
- (void)setFoo:(NSUInteger)newFoo
{
_foo = newFoo;
}
#end
is equivalent to:
#interface MyClass
#property (nonatomic, assign) NSUInteger foo;
#end
You save a lot of typing. When you get into things like NSString properties and different property modifiers like strong or copy, the amount of code you save (and memory management mistakes you avoid) in the mutators becomes much greater.
Your .h file should be your public api. You can re-declare your properties in your .m, implementation file, which are also considered private. For example
.h
#interface MyViewController : UITableViewController
#property (readonly) NSString *name;
#end
.m
#implementation MyViewController
#property (readwrite) NSString *name
#end
Here we are declaring a public name property that is readonly and in your implementation you 're re-declaring the property so that you can use the setter accessor.
Xcode 4.6.1 iOS 6 using storyboards
My problem is this
I have a UITableView with dynamic prototype cells on a UIView in a UIViewController (that is itself embedded in a navigation controller) and I want to segue from one specific cell to another view
(Before anyone suggests I should just be using a UITableViewController , I do have other things on the UIView, so i'm set up this way for a reason.)
Now i'm not sure how to go about creating the segue
If I drag from the prototype UITableViewCell to create a segue , all the generated cells automatically call the the segue - when i need only one to do so. This is normal behaviour and I would get around this if i was using a UITableViewController by creating the segue by dragging from UITableViewController and calling [self performSegueWithIdentifier:.... From my didSelectRowAtIndexPathMethod so only the specific cell I want to perform this segue triggers it.
I don't have a UITableViewController in this case - just my UITableView on a UIView that is part of a UIViewController subclass
I've been playing around and I have just discovered that i cannot drag from the UITableView - doesn't let you do that, so that was a deadend.
My only choice that seemed left to me was to drag from the UIViewController
So i tried that and of course XCode throws up an error on the perform segue line telling me i have ... No visible interface for 'LocationTV' declares the selector performSegueWithIdentifier. LocationTv being my tableview subclass.
What is the correct way to attempt to call the new view in this situation
Thank
Simon
First of all segues can be use only between UIViewControllers. So in case you want to perform a segue between two views that are on the same view controller, that's impossible.
But if you want to perform a segue between two view controllers and the segue should be trigger by an action from one view (inside first view controller) well that's possible.
So in your case, if I understand the question, you want to perform a segue when the first cell of a UITableView that's inside of a custom UIView is tapped. The easiest approach would be to create a delegate on your custom UIView that will be implemented by your UIViewController that contains the custom UIView when the delegate method is called you should perform the segue, here is a short example:
YourCustomView.h
#protocol YourCustomViewDelegate <NSObject>
-(void)pleasePerformSegueRightNow;
#end
#interface YourCustomView : UIView {
UITableView *theTableView; //Maybe this is a IBOutlet
}
#property(weak, nonatomic) id<YourCustomViewDelegate>delegate;
YourCustomview.m
#implementation YourCustomview
# synthesise delegate;
//make sure that your table view delegate/data source are set properly
//other methods here maybe
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row == 0) { //or any other row if you want
if([self.delegate respondsToSelector:#selector(pleasePerformSegueRightNow)]) {
[self.delegate pleasePerformSegueRightNow];
}
}
}
YourTableViewController.h
#interface YourTableViewController : UIViewController <YourCustomViewDelegate> {
//instance variables, outlets and other stuff here
}
YourTableViewController.m
#implementation YourTableViewController
-(void)viewDidLoad {
[super viewDidLoad];
YourCustomView *customView = alloc init....
customView.delegate = self;
}
-(void)pleasePerformSegue {
[self performSegueWithIdentifier:#"YourSegueIdentifier"];
}
You can create any methods to your delegate or you can customise the behaviour, this is just a simple example of how you can do it.
My Solution
I ended up using a delegation pattern
I made a segue dragging from the my UIViewController - specifically dragging from the viewController icon (the orange circle with a white square in it - from the name bar thats under the view in the storyboard - although you could also drag from the sidebar ) to the view that i wanted to segue to.
I needed to trigger this segue from a table view cell on a table view.
TableView Bit
So i declared a protocol in my tableview header file - which is called LocationTV.h - as follows
#protocol LocationTVSegueProtocol <NSObject>
-(void) makeItSegue:(id)sender;
#end
Below that I declare a property to hold my delegate
#property (nonatomic, strong) id<LocationTVSegueProtocol> makeSegueDelegate;
To actually trigger the segue i called the makeItSegueMethod on my makeSequeDelegate in my didSelectRowAtIndexPath method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {
DLog(#"selected row %d",indexPath.row);
case dLocation:
{
if(indexPath.row == 2){
[_makeSegueDelegate makeItSegue:self];
} else if (indexPath.row == 7){
UIViewController Bit
and set up my UIViewController (named MultiTableHoldingVC) as implementing that protocol
#interface MultiTableHoldingView : UIViewController
<EnviroTVProtocol,LocationTVSegueProtocol> {
}
Below that i declared the protocol method in the list of my classes methods (although i'm not sure that is necessary as the compiler should know about the method as the decalration of implementing a protocol is essentially a promise to implement this method)
-(void) makeItSegue:(id)sender;
And then over in the implementation file of my UIViewController i wrote the method which essentially just calls preformSegueWithIdentifier
-(void) makeItSegue:(id)sender{
[self performSegueWithIdentifier:#"ChooseCountryNow"
sender:sender];
}
And to link it all together,as in the header file I had declared my instance of the tableView as follows
#property (strong, nonatomic) IBOutlet LocationTV *dsLocationTV;
I had to set that tables views delegate property to be self - which I did in my UIViewControllers -(void)ViewDidLoad method
_dsLocationTV.makeSegueDelegate = self;
It all seems a bit of a kludge calling a method to call a method and allprog suggestion is simpler (I cant for the life of me work out why it threw up errors for me) but this works just fine . Thanks to both allprog and danypata for their suggestions.
Hope this is helpful to someone out there
performSegueWithIdentifier: is a method of the UIViewController class. You cannot call it on a UITableView instance. Make your view controller implement the UITableViewDelegate protocol and set it as the delegate for the UITableView.
Another option is that you don't use segues. In the same delegate method do:
OtherViewController ov = [[OtherViewController alloc] init<<some initializer>>];
// Or in case of storyboard:
OtherViewController ov = [self.storyboard instantiateViewControllerWithIdentifier:#"ovidentifier"];
// push view controller
[self.navigationController pushViewController:ov animated:YES];
If the delegate object is different from the view controller, then the easiest solution is to add a weak property to the delegate's class that keeps a reference to the viewController, like this:
#property (weak) UIViewController *viewController;
and set it up in the viewDidLoad of the viewController
- (void) viewDidLoad {
self.tableView1.viewController = self;
}
Make sure that the tableView1 property is declared like this:
#property (IBACTION) (weak) SpecialTableView *tableView1;
Sometimes using the storyboard is more painful than writing the code yourself.