Programmatically select itself inside UITableViewCell - ios

I have a UITableViewCell with a UICollectionViewCell inside. I want the user to be able to scroll the UICollectionViewCell but when he taps anywhere inside the UITableViewCell I need it to be selected. Currently when the user taps the UITableViewCell outside the UICollectionViewCell it is selected properly, but when he taps inside the UICollectionViewCell nothing happens. My idea is to implement the collectionView: didSelectItemAtIndexPath: method inside the UITableViewCell and programmatically trigger a "self selection", but I can't seem to find a way to do this. If I store a reference to the table and the indexPath of the cell inside itself I will probably be able to do it, but I have a feeling that this would be a bad way of doing it. How to do this properly?

My guess is that the UITableViewCell's didSelectRowAtIndexPath: is blocking the UICollectionViewCell's collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:
I would first check if that's the case by putting breakpoints for the UITableViewCell's didSelectRowAtIndexPath: while selecting the UICollectionViewCell.
If that's the case, this answer might help you (comes with a nice tutorial too): https://stackoverflow.com/a/17120673/5465258
I think they have a similar problem to what you had.

Well, I found a solution for this, though I don't know if it's the best one.
First, I added a UITapGestureRecognizer to the UICollectionView inside my UITableViewCell, to override the component's one.
-(void)awakeFromNib
{
...
UITapGestureRecognizer *v_CollectionViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onClickCollectionView)];
[self.m_CollectionView addGestureRecognizer:v_CollectionViewTap];
...
}
Second I created a block property to hold the code for the programatic selection of a row and created a method that calls this block, setting it as the action for the previously created UIGestureRecognizer.
typedef void (^OnClickTableViewCellBlock)();
#property (nonatomic, copy) OnClickTableViewCellBlock m_OnClickBlock;
Last, when I'm creating the UITableViewCell, in the tableView:cellForRowAtIndexPath: method, I pass a block that calls the tableview:didSelectRowAtIndexPath: one.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
v_Cell.m_OnClickBlock = ^void()
{
[tableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
};
...
}

My solution was essentially the same as accepted answer, except I'm using Swift and decided to use delegation.
Inside your UITableViewCell subclass - CustomCell in this case - create your delegate as below (making sure to add a property for IndexPath), and wire up the UITapGestureRecognizer similar to the accepted answer:
protocol CustomCellDelegate {
func collectionViewTapped(forIndexPath indexPath: IndexPath)
}
class CustomCell: UITableViewCell {
...
var delegate: CustomCellDelegate?
var indexPath: IndexPath?
#objc func onTapCollection() {
if let indexPath = indexPath {
delegate?.collectionViewTapped(forIndexPath: indexPath)
}
}
override func awakeFromNib() {
super.awakeFromNib()
let collectionViewTap = UITapGestureRecognizer(target: self, action: #selector(onTapCollection))
collectionViewTap.numberOfTapsRequired = 1
self.collectionView?.addGestureRecognizer(collectionViewTap)
}
...
}
And then just have your UITableViewController implement your CustomCellDelegate, and assign the delegate and IndexPath for your cell in tableView(_:cellForRowAt:):
class MyViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
// cell here is my CustomCell that I set up
cell.indexPath = indexPath
cell.delegate = self
return cell
}
...
}
extension MyViewController: CustomCellDelegate {
func collectionViewTapped(forIndexPath indexPath: IndexPath) {
self.tableView(self.tableView, didSelectRowAt: indexPath)
}
}

Related

How to detect if TableViewCell has been reused or created

In Swift with dequeueReusableCell API we don't have control over creating of a new instance of TableViewCell. But what if I need to pass some initial parameters to my custom cell? Setting parameters after dequeue will require a check if they have been already set and seem to be uglier than it was in Objective-C, where it was possible to create custom initializer for a cell.
Here is a code example of what I mean.
Objective-C, assuming that I don't register a class for the specified identifier:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* reuseIdentifier = #"MyReuseIdentifier";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (!cell)
{
cell = [[MyTableViewCell alloc] initWithCustomParameters:...]; // pass my parameters here
}
return cell;
}
Swift:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyReuseIdentifier")
if let cell = cell as? MyTableViewCell {
// set my initial parameters here
if (cell.customProperty == nil) {
cell.customProperty = customValue
}
}
}
Do I miss something or it's how it supposed to work in Swift?
In swift or objective-c dequeueReusableCell will return a cell if there is an available 1 or will create another if there isn't , btw what you do in objc can be done in swift it's the same
Always before UITVCells will reuse inside your Cell class will prepareForReuse() called. You can use this method to reset all content like imageView.image = nil.
Use the initial from UITVCell init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) to know if the cell was created.
If you want to know this infomations inside your tableView class, use func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) delegate method.
PS: Dont forget to call super.
The working approach is basically the same as Objective-C: Do NOT register cell for "MyReuseIdentifier" and use dequeueReusableCell(withIdentifier: )
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "MyReuseIdentifier")
if cell == nil {
cell = MyTableViewCell.initWithCustomParameters(...)
}
return cell
}

How do I embed a UITableView in a UIView?

I have a UIView in a custom view that I built using xib. I need to display a UITableView in the said view. At first I thought about placing a container and embedding a UITableViewController in it. Turns out I cannot place containers in a xib file, or atleast there's no way of doing it from the IB as it doesn't show up in views section at the lower right.
I can create a UITableView programmatically and add it as a subview of the view. It shows up as expected but I cannot seem to be able to add cells in it. I also tried creating a well behaving UITableViewController in association with a storyboard view, instantiate that controller as follows:
let storyboard = (UIStoryboard(name: "Main", bundle: nil))
let vc = storyboard.instantiateViewControllerWithIdentifier("tableViewController") as! TestTableViewController
and then tried accessing the UITableView's outlet which was nil. Then I read somewhere that I should do a vc.loadView() because as the name suggests, it loads the view and my IBOutlet would not be nil. This worked. The outlet was on longer nil. But, when I add the table in the container view as a subview, it still shows no cells. There are only separator lines but no content. I've run out of ideas!
EDIT
I do not have any UITableViewCell implementations as the tables are static.
Good approach is to use UITableview inside your custom view:
if you are adding tableview programmatically then register your cell using Nib or UITableview subclass like
tableView.registerNib(UINib(nibName: "UITableViewCellSubclass", bundle: nil), forCellReuseIdentifier: "UITableViewCellSubclass")
for if you are creating UITableviewCell using Xib.
tableView.registerClass(UITableViewCellSubclass.self, forCellReuseIdentifier: "UITableViewCellSubclass") // using code.
tableView.delegate = self
tableView.dataSource = self
and then use 2 required delegates.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return tableView.dequeueReusableCellWithIdentifier("UITableViewCellSubclass", forIndexPath: indexPath) as! UITableViewCellSubclass
}
hope i answered your question.
Objective c
You need delegates in your viewcontroller, if you have a viewcontroller, put delegate of table :
UIViewController
UITableViewDelegate
UITableViewDataSource
And you can use your functions
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1; //count of section
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [catagorry count]; //count number of row from counting array hear cataGorry is An Array
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the provided setImageWithURL: method to load the web image
// Ensure you use a placeholder image otherwise cells will be initialized with no image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://example.com/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder"]];
cell.textLabel.text = #"My Text";
return cell;
}
Swift :
delegate:
UIViewController
UITableViewDataSource
UITableViewDelegate
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Swift
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath)
let row = indexPath.row
cell.textLabel?.text = swiftBlogs[row]
return cell
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath)
let row = indexPath.row
cell.textLabel?.text = swiftBlogs[row]
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return swiftBlogs.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
Look more More inf

Prevent voice over (Accessibility) from announcing UITableViewCell as selected

When a UITableViewCell is selected, voice over announces "selected", I don't want voice over to say "selected". How can i achieve this ?
Things I have tried with no success:
Changed the cell accessibilityHint and accessibilityLabel
Changed the cell selectionStyle = UITableViewCellSelectionStyleNone
changed the cell accessibilityTraits = UIAccessibilityTraitButton
Question:
I don't want voice over to say "selected" when a cell is selected. How can i achieve this ?
I asked this as a code level support issue from Apple and got the following solution which works perfectly. Use a custom subclass of UITableViewCell where you override accessibilityTraits as in the following example:
class NoTraitCell: UITableViewCell {
override var accessibilityTraits: UIAccessibilityTraits {
get {
return UIAccessibilityTraitNone
}
set {}
}
}
You could try by deselecting the cell again:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
You can create custom cell class and override accessibilityTraits like this:
- (UIAccessibilityTraits)accessibilityTraits {
return UIAccessibilityTraitButton;
}
If you don't intend to use the selection feature of the tableview then don't use tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). I know I've always used this as a "didTapOnRowAt" method, but a better way is to use willSelectRowAt:
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// code for when a row is tapped
return nil
}
The return nil means the cell won't actually be selected.
What worked for me was to set the cell's accessibilityLabel to " " (empty string doesn't work) on didSelectRow(), trigger a reload, then reset accessibilityLabel on next dequeue.
You can use accessibilityElementsHidden property for disabling voice accessibility.
If you don't want to hear a view in voice-over mode set the accessibilityElementsHidden property to true for that particular view(documentation)
In your case, for a UITableViewCell you can set it as true in tableView(_:cellForRowAt:) method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// write code to create cell ....
cell.accessibilityElementsHidden = true
return cell
}
Note: You can also set the property in awakeFromNib() method for a custom class table view cell.
The only work around is prevent cell selection
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
return nil;
}
Add a Tap gesture on the cell and when the cell is tapped, do the what ever you want in cell selection inside the tap gesture.

How do I get the name of a selected collectionviewcell?

This is a simple question that I thought would have an easy-to-find answer but didn't. I want to select a cell in a collectionview. The main problem is that I can't attach a gesture recognizer to a prototype cell. I want to grab the text from a label on the cell that is touched. I use the name in a different function in my view.
Or a simpler question: Is there a tutorial on tap selection from a list of items?
You have the method collectionView:didSelectItemAtIndexPath: in the delegate. This should fire when you collect the cell and give you the correct indexPath for that particular cell.
Use this indexPath in combination with the collectionView's cellForItemAtIndexPath: to access a particular cell.
Example:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self manipulateCellAtIndexPath:indexPath];
}
-(void) manipulateCellAtIndexPath:(NSIndexPath*)indexPath {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
// Now do what you want...
}
And, as long as I'm here. Swift-version:
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
manipulateCellAtIndexPath(indexPath)
}
func manipulateCellAtIndexPath(indexPath: NSIndexPath) {
if let cell = collectionView?.cellForItemAtIndexPath(indexPath) {
// manipulate cell
}
}

Fire a method from a Static Cell in a Table view Controller

In my code i have a table with static cell inside storyboards. I'm trying to fire a method upon clicking the last static cell.
What should i write in the code to make this happen. How can i refer static cells inside the code without firing error.
In the viewController add:
#property (nonatomic, weak) IBOutlet UITableViewCell *theStaticCell;
Connect that outlet to the cell in the storyboard.
Now in tableView:didSelectRowAtIndexPath method:
UITableViewCell *theCellClicked = [self.tableView cellForRowAtIndexPath:indexPath];
if (theCellClicked == theStaticCell) {
//Do stuff
}
With static cells, you can still implement - tableView:didSelectRowAtIndexPath: and check the indexPath. One approach, is that you define the particular indexPath with #define, and check to see whether the seleted row is at that indexPath, and if yes, call [self myMethod].
Here is my take when mixing static and dynamic cells,
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let staticIndexPath = tableView.indexPathForCell(self.staticCell) where staticIndexPath == indexPath {
// ADD CODE HERE
}
}
this avoids creating a new cell.
We all are used to create the cell and configure it in cellForRowAtIndexPath
Following CiNN answer, this is the Swift 3 version that solves the issue.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let staticIndexPath = tableView.indexPath(for: OUTLET_TO_YOUR_CELL), staticIndexPath == indexPath {
// ADD CODE HERE
}
}
this approach allow to not necessary implement cellForRow method, specially if you are using static cells on storyboard.
I think you were having the same problem I was. I kept getting an error when overriding tableView:didSelectRowAt, and the reason was that I'm used to just calling super.tableView:didSelectRowAt, but in this case we don't want to do that. So just remove the call to the super class method to avoid the run-time error.

Resources