So far I´ve only needed to implement prototype cells with pre-defined designs (normal, subtitle, etc.) and it hasn´t been a problem.
Now I need to implement prototype cells which contain some controls like a segmented switch, a switch or any other. The problem is that I haven´t been able to find out how the actions triggered are implemented and how are they related to the control. Also I heven´t found any example of how different prototype cells inside a single UITableViewController are implemented.
I know it´s kind of a generic question, but I´d appreciate some pointers here. Maybe someone knows about some documentation, tutorial, etc. Well, any help would do,
Thnaks in advance.
It took me also a while to understand how to use the prototype cells. If you want to access the user interface elements inside a prototype cell, you have to create a subclass of UITableViewCell and assign it to the prototype cell in Interface Builder.
Then you have to define the IBOutlet properties manually e.g.:
#interface OptionSwitchCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UISwitch* switchControl;
#end
After that, you can connect the interface elements through control-dragging from the element to the property definition in the assistant view.
The IBActions instead can be defined inside the owning View Controller. You can control-drag from a interface element to the View Controller header file and create an action. Inside the action implementation you will likely want to know which cell was been triggering the action. I do it like this:
#implementation SomeTableViewController
- (IBAction)toggleActivity:(id)sender {
OptionSwitchCell* cell = (OptionSwitchCell *)[sender superview].superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
...
}
#end
Another solution for finding the corresponding cell and index path (by jrturton):
- (IBAction)toggleActivity:(id)sender {
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
OptionSwitchCell* cell = (OptionSwitchCell *)[self.tableView cellForRowAtIndexPath:hitIndex];
...
}
Although this is a bit quirky, I haven't found a better solution so far. Hope that helps.
Related
I have a collection view, and I am trying to get the index of the cell that I am peeking and poping from.
Issue
Currently I am using indexPathForItemAtPoint: however this always returns 0 no mater where I tap on the screen.
Code
collection view controller:
- (void)viewDidLoad {
[super viewDidLoad];
[self registerForPreviewingWithDelegate:self sourceView:self.collectionView];
}
- (UIViewController *) previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
CellEditViewController *CEVC = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"]; //The view I want peek/pop to
NSLog(#"Location: %f,%f", location.x, location.y);
NSLog(#"Index of: %lu", [[self.collectionView indexPathForItemAtPoint:location] row]);
[CEVC setPreferredContentSize:CGSizeMake(0.0, 320.0)];
return CEVC;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
[self showViewController:viewControllerToCommit sender:self];
}
What I have tried
Creating a new location to identify the index cell.
Moving registerForPreviewingWithDelegate:sourceView: to where I create each cell.
Moving previewingContext:viewControllerForLocation: and previewingContext:commitViewController: to the cell view method, this did not work for other reasons.
I do not think this is an issue with previewing, because when I implemented the same thing with a UITapGestureRecognizer, I got a similar output:
Tap recognizer:
- (void) processDoubleTap:(UITapGestureRecognizer *)sender {
NSLog(#"Got tapped twice");
if (sender.state == UIGestureRecognizerStateEnded) {
CGPoint point = [sender locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:point];
NSLog(#"Index was: %lu", [indexPath row]);
if (indexPath) {
NSLog(#"Index was double tapped");
}
}
}
Output:
2017-12-25 10:48:13.990523-0800 quickthings[3052:356150] Got tapped twice
2017-12-25 10:48:13.990843-0800 quickthings[3052:356150] Index was: 0
Source
Github Repository
Screenshot
Here is what does happen, this is exactly what I want. The only other thing I would like to do is when the cell is tapped also be able to get the index of the tapped cell.
Collection View In Story Board
The (blue) UIView is "linked" to the Collection View Controller (the top view controller in the second screenshot below).
The Cause:
The main cause of the problem is how the CollectionViewController is setup in the storyboard. In summary these are the main issues with it:
The CollectionViewController was added as a UIViewController and then the class changed to CollectionViewController but the problem is that is a subclass of a UICollectionViewController. So there is a miss match between what the storyboard thinks it is and what it actually is. You should have added it as a UICollectionViewController to begin with.
The CollectionViewControllers top view has had it's class changed from UIView to UICollectionView which I assume was to match how the UICollectionViewController is setup. So again there is a miss match here meaning you can't see any of the correct properties in the Interface Builder.
There is then an additional UICollectionView added as a sub view of the main CollectionViewController main view. This has its data source and delegate linked into the CollectionViewController class and is the one that is actually being displayed. It's all setup correctly with a prototype cell. However it's not linked into the class as an IBOutlet so you can't reference it.
So why is this causing the problem you are seeing. In the CollectionViewController when you refer to self.collectionView you are referring to the top level one which is not the one that is displaying the cells. That's not the one displaying the cells and in fact has no displayed cells so when you ask for the cell at a particular point it will always return nil and hence you get a row value of zero. If you were able to get access to the UICollectionView actually displaying the cells you could get the correct value.
Other than that there will be other issues that you haven't come across yet.
The Solution:
There are basically two main solutions to this problem...
Remove the CollectionViewController from the storyboard and add it again as a UICollectionViewController. This of course is the ideal way to resolve it as it will all be setup correctly however it will require re-creating the entire controller in the storyboard again and is not absolutely necessary as you can do method 2...
You can modify your storyboard and setup to correctly reflect the setup you have and get it working.
I have tried method 2 and this is what I had to do to get it working correctly:
Change the CollectionViewController from subclassing UICollectionViewController to subclassing UIViewController as it will just become a holder for the UICollectionView. You also need to make it support UICollectionViewDataSource and UICollectionViewDelegate directly as the UICollectionViewController already does this but the UIViewController doesn't. So change this line in CollectionViewController.m:
#interface CollectionViewController : UICollectionViewController
to this:
#interface CollectionViewController : UIViewController <UICollectionViewDelegate, UICollectionViewDataSource>
Next in the storyboard change the top level view of the CollectionViewController back to a UIView (i.e. delete the UICollectionView in the class property). It's now just a holder for the real UICollectionView.
Next link the remaining UICollectionView that is setup in the storyboard to an IBOutlet in CollectionViewController.h called 'collectionView' so that all the references to self.collectionView are now referring to the correct thing.
One extra thing I had to do but I'm not sure why is change the cell identifier for the UITableViewController and UICollectionViewController so that they are not both 'Cell'. I just used 'TableCell' and 'CollectionCell'. If you don't do this they both get confused for some reason.
(I think that is all I did but if not I'm sure you will be able to handle any other issues)
Once all that has been done then the self.collectionView will be referring to the correct thing and you will get the correct index path in the - (UIViewController *) previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location method.
Bonus Round:
Now some extra bonus comments. You appear to also have the same kind of issue with the TableViewController although it doesn't appear to be causing any issues at the moment. You may want to fix it though.
When I first ran the project I could not see anyway to add anything to the table because the text field and 'add' button were missing. This turned out to be because I was using an iPhone 8 simulator not an iPhone X simulator and once I switched everything was ok and I could see the text field and 'add' button. What this means is your interface is not adapting to the different device sizes correctly (at all really) and you should really address that.
Phew that ended up longer than I thought but hopefully will clear up the problem. Also as I had access to the git repository I could clone it and make my changes. I obviously haven't committed them as it's your repository but if you have any trouble understanding what I have done to get it to work I can commit them ether into your main branch or as a new branch.
I'm trying to reuse a UITableViewController for multiple purposes. My problem is that I'd like to display different buttons and other elements depending on the intention for displaying the list.
I'm currently using multiple cell prototypes to display different info for each item's detail, but I'd also like to be able to swap the controls depending on intention. I've been showing and hiding controls to accomplish this, but looking at the storyboard gets a bit ugly.
I was thinking maybe using a container view... just figured I throw this out there and see if anyone is doing anything similar. I didn't want to created separate list views just to change buttons.
Without more details, it's hard to say whether this will solve your problem particularly. However, I do precisely this in my current project and this is how:
I am going to assume the following. Let me know if they don't hold true and I'll update my answer accordingly.
You're creating a cell prototype for every permutation and combination, including actions. And so you have too many prototype cells.
Your actions are 'buttons' or similar controls on the cell (and not the swipe to reveal edit actions).
Create a custom class for your cell. Add a container for your actions and position it appropriately in story board. Connect the container to an outlet in the cell.
#interface MyCustomCell : UITableViewCell
#property MyCellTypeEnum type;
-(void) configure;
#end
#implementation MyCustomCell
-(void) configure {
switch(type) {
case type1:
// add actions to container
break;
case type2:
// etc.
}
}
#end
And in your TableViewController's cellForRowAtIndexPath do the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ... dequeue appropriate cell
cell.type = <appropriate type>
[cell configure];
return cell;
}
Hope that helps.
I have a UITableViewController, designed entirely with Interface Builder. The UITableViewController is called DonationTableView. I am using Static cells and the UITableView has 6 sections. The reason I am using Static cells is because the content can be populated right from within the Interface Builder and because each cell contains:
1) A different size
2) A label with it's own text
3) A button
I can also use AutoLayout easily within Interface Builder to make sure this DonationTableViewController looks appropriate on all devices. I know I could use code, but I'm still a newbie here and I'm confident with Interface Builder.
I have a custom UITableViewCell class called DonationTableViewCell and I have assigned that to the UITableViewCell in Interface Builder. I am now trying to create an IBAction and IBOutlet for the UIButton in each cell, but with Assistant Editor up, it won't let me actually drag to create that IBAction in the way you usually do. If I change the UITableView to Dynamic, it then allows me to do that, but as mentioned above, I have a fully working UITableView with Static Cells and I just want to create a delegate method in the custom UITableViewCell class so that I can click the button and run an action.
So essentially, I want to be able to assign an IBAction to the UIButton in the UITableViewCell. How would I go about doing that?
Any thoughts would be appreciated.
Drag the button outlet or IBAction to your TableviewController
For use UiButtonin Tableview you need to follow this 2 Steps.
(1) Give UIButton Tag Inside Tableview.
Ex : Here your Button is identify with uniq tag .And also Give UIButton Method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cell.button.tag = indexPath.row;
[cell.button addTarget:self action:#selector(btn_Clicked_Method:)forControlEvents:UIControlEventTouchUpInside];
}
(2) In UIButton Method
- (IBAction)btn_Clicked_Method:(id)sender {
NSInteger tag= ((UIButton *)sender).tag
NSLog(#"Button row %ld",(long)tag);
}
So far, have read a few posts, such as this and this, but they have not really helped with my situation.
I'm creating a dynamic form for iPad using 'plain' style UITableViews. I have multiple different UITableViews on the page, so I defined a separate object to server as my datasource and delegate. I understand how to change the text of each cell using the datasource; however, I have no clue how to link the UITextFields in my prototype cells to an IBAction. I could figure out how to create a single IBAction for all textfields in my table, such that they all update the same data, but I don't know how to have each UITextField have a one-to-one correspondence with my datasource.
Here is my prototype cell:
and my code thus far:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myPrototypeCell"];
UILabel *buildingNumber = (UILabel *)[cell viewWithTag:100];
buildingNumber.text = [#"Building " stringByAppendingString:self.dataSource[indexPath.row][#"buildingNumber"]];
return cell;
}
self.dataSource is an NSMutableArray of NSMutableDictionaries.
Any help whatsoever is appreciated.
I initially thought you were referring to IBOutlets so my previous answer is somehow wrong but the inherent idea is still the same.
You cannot have IBActions or IBOutlets from a prototype cell unless the cell is subclassed. You can do so if the cells are static though, not that it can help in your case. Subclassing the UITableViewCell is not too hard or too bad, in fact if in the future you want to speed things up on your TableView, that is one of the many ways to start.
This tutorial provides a few different options for dealing with information inside a table view cell:
http://mobile.tutsplus.com/tutorials/iphone/customizing-uitableview-cell/
I almost always use a UITableViewCell subclass to deal with outlets and actions inside the cell. But this should be a decision you make based on your own architecture.
Hope this helps!
you need single for all your textField.So do the following:
Get the text field as your are getting label
UITextField *yourTextField = (UITextField *)[cell viewWithTag:101];
[yourTextField addTarget:self action:#selector(clickTextField:) forControlEvents:UIControlEventEditingChanged];
clickTextField method will get invoke for every Text Field.
Hope this helps.
Edit: Forgot to mention, you can you set delegate of UItextField and get a notification in UITextFieldTextDidChange: delegate method
can someone please explain why you should use viewWithTag to get subviews (e.g. UILabel etc) from a cell in dequeueReusableCellWithIdentifier?
Some background info: I've got a custom UITableViewCell with a couple of UILabels in it (I've reproduced a simple version of this below). These labels are defined in the associated NIB file and are declared with IBOutlets and linked back to the custom cell's controller class. In the tableview's dequeueReusableCellWithIdentifier, I'm doing this:
CustomCell *customCell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:#"CustomCellId"];
if (customCell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"customCell" owner:self options:nil];
for (id oneObject in nib)
if ([oneObject isKindOfClass:[CustomCell class]])
customCell = (CustomCell *)oneObject;
}
customCell.firstLabel.text = #"Hello";
customCell.secondLabel.text = #"World!";
return customCell;
Everything works fine. However from the tutorials I've seen, it looks like when changing the labels' values I should be doing this instead:
UILabel *firstLabel = (UILabel *)[customCell.contentView viewWithTag:555];
firstLabel.text = #"Hello";
UILabel *secondLabel = (UILabel *)[customCell.contentView viewWithTag:556];
secondLabel.text = #"World!";
(The labels' tag values have been set in the NIB).
Can someone tell me which method is preferred and why?
Thanks!
viewWithTag: is just a quick and dirty way to pull out child views without having to set up IBOutlet properties on the parent, or even without having to create a UITableViewCell subclass.
For very simple cases this is an acceptable solution, that's what viewWithTag: was intended for. However if you are going to reuse that cell a lot or you want it to have a more developer-friendly interface then you will want to subclass and use real properties as in your first example.
So use viewWithTag: if it's a very simple cell you designed in IB with no subclass and with just a couple of labels. Use a cell subclass with real properties for anything more substantial.
I've realised that it's useful to retrieve elements using "viewWithTag" if the elements were added to the cell programmatically (i.e. not defined in a NIB and hooked-up via IBOutlets)—this prevents multiple labels etc. to be created for each instance of the cell.
For me , viewWithTag is a God given. First of all : treating all views in a loop like taskinoor said is really easy. Also , I personally prefer this way because if I take a look on the code and want to see what happens with a view , I simply search for the tag. It's used everywhere the view is handled. Opposed to the xib approach where you have to look in the code and xib too. Also , if you have an offscreen view in a xib , you might oversee it.
I found a lot of xibs made by other programmers that were FULL with lots and lots of views. Some hidden , some offscreen , couldn't tell which is which since there were all overlapping.
In those cases , I think xibs are bad. They are not easy to read anymore.
I prefer everything made in code.
But if you decide to work with tags, remember to avoid hard-coding any tag. Instead make a list of #define definitions to keep the code clean and readable.
I always hook subviews to properties of my UITableViewCell subclass via IBOutlets, as you have done. I can't think of any good reason to use viewWithTag.
From UITableViewCell Class Reference: "The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell." Keep it simple, clear out the content view. This makes no assumptions about custom cell classes, no casts, no class inspection:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell != nil)
{
NSArray* contentSubViews = [cell.contentView subviews];
for (UIView* viewToRemove in contentSubViews)
{
[viewToRemove removeFromSuperview];
}
}
viewWithTag: allows styling without creating a custom subclass of UITableViewCell.
You can assign a tag and reuse identifier to a prototype UITableViewCell in Interface Builder, then dequeue and modify the view with that tag within the implementation of your UITableViewController, without creating a custom class for that cell or creating IBOutlets for the cell's subviews.
In some cases, the simplicity of a cell makes a custom class feel like overkill. viewWithTag: allows you to add custom text and image to a cell in the Storyboard, then set those customizations via code, without adding extra class files to your Xcode project.