Playing songs with button in UITableView - objective-c

I'm creating a custom table that has a button which allows a user to preview a song when pressed. Most of my code works but I haven't figured out how to pass the player a particular song corresponding to the row in which the button was pressed.
For instance: if I have two rows and #1 says Jay Z and #2 says Red Hot Chili Peppers, I want to press the button in #1 to play Jay and to press the button in #2 for the Peppers. Simple. My code is flawed and no matter which row's button I press I can only get the same song to play.
I know why that's happening, but I don't know how to solve it. I'm just wondering if anyone could hit me with a few lines that could point me in the right direction.
I can't use didSelectRowAtIndexPath because I want something else to happen when the row itself is selected.
Will I need to create a method for this or is there something I've overlooked?
Thanks!

You could also set the tag property of each button you create during tableView: cellForRowAtIndexPath:, then when your buttonTapped event is called, look up the sender and find its tag. The tag property of UIView was provided for just this sort of problem.
If you need more information than that, you could create a UIButton subclass that stores any or all information needed about the associated song. Once again, you set that information during cellForRowAtIndexPath, to be retrieved when the button is tapped.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
// Dequeue a cell and set its usual properties.
// ...
UIButton *playButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[playButton addTarget:self action:#selector(playSelected:) forControlEvents:UIControlEventTouchUpInside];
// This assumes you only have one group of cells, so don't need to worry about the first index. If you have multiple groups, you'll need more sophisticated indexing to guarantee unique tag numbers.
[playButton setTag:[indexPath indexAtPosition:1]];
// ...
// Also need to set the size and other formatting on the play button, then make it the cell's accessoryView.
// For more efficiency, don't create a new play button if you dequeued a cell containing one - just set its tag appropriately.
}
- (void) playSelected:(id) sender;
{
NSLog(#"Play song number %d", [sender tag]);
}

Something like
- (void)buttonTapped:(UIView *)sender;
{
CGPoint pointInTableView = [sender convertPoint:sender.bounds.origin toView:self.tableView];
NSIndexPath *tappedRow = [self.tableView indexPathForRowAtPoint:pointInTableView];
// get song that should be played with indexPath and play it
}

something like in tableView: cellForRowAtIndexPath: give your button tag as index.row and bind the function below to button's touchup inside event
-(void)button_click:(UIView*)sender
{
NSInteger *index = sender.tag;
//play song on that index
}
I think this will help you!

Related

get indexPath for customCell in TableView

I have a UITextField in a customCell. I want to be able to get the text that user inputs in it. In my customCell.m file I can get the text that is inputed with
[self.aTextField addTarget:self action:#selector(aTextFieldEditing:) forControlEvents:UIControlEventEditingChanged];
but I cannot get the indexPath so I can save it to the NSMutableArray that holds all the UITextFields.
In my TableView I cannot get the didSelectRowAtIndexPath to run. I also cannot get the textFieldDidBeginEditing method to output anything. If it helps I have a TableView with sections.
My idea at the moment is to get the indexpath.row and indexpath.section from the tableView and save them as an extern so I can access it in the customCell.m
I would be grateful for any ideas or specific examples of how I could do this. Or a different cleaner way to accomplish what I want.
You should be able to get the index path using the -indexPathForRowAtPoint: method on UITableView.
You can do something like this (not tested):
- (void)aTextFieldEditing:(UITextField *)textField {
CGPoint convertedPoint = [textField.superview convertPoint:textField.center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:convertedPoint];
// Use your index path...
}
Edit:
In the case your cell is receiving the edit events, you can always define a protocol and make your view controller a delegate of the cell. You could then call this delegate method from your aTextFieldEditing method and pass the cell. Now that you have the cell you can call -indexPathForCell on your table view. If none of this makes sense, look into the delegate pattern. It's very common in Cocoa/Cocoa Touch and is very well documented online.

How to get textFields in custom UITableView

Very beginner obj-c question.
I have plain UITableView with two sections, but I am interested only in first section now. This section have four custom cells (inherited from standard UITableViewCell), and they have a UITextField's as a property.
I need to improve custom Input Accessory View with buttons "Next", "Previous"(for switch between textFields in tableview) and "Done" (dismissimg of keyboard). http://uaimage.com/image/62f08045
In -textFieldShouldReturn i set tags for textFields from 0 to 3. My next plan is to add textFields into NSMutableArray in -viewDidLoad and then just set and resign first responder for the textFields. Approximate code listing for "Next" button:
- (void) inputAccessoryViewDidSelectNext:(FDInputAccessoryView *)view {
for (UITextField *textField in [self textFieldsArray]) {
if ([textField isFirstResponder]) {
[textField resignFirstResponder];
UITextField *field = [[self textFieldsArray] objectAtIndex:textField.tag + 1];
[field becomeFirstResponder];
}
}
}
Questions:
Is this a right way or maybe there is a better approach to solve problem?
Do I need to tag textfields or use indexPath of cells in what they are built in? (or what is the best to track textFields?)
And the main question: what is the syntax to "get" textField from cell?
Sorry for the dumb questions, I am a very beginner.
Thanks,
Alex.
I think you have the right idea, but a few things come to mind:
Just to be safe, don't start with tag number 0. Every view has a tag number defaulted to 0, so you may get something unexpected.
Don't set the text view's tags inside of the textFieldShouldReturn, set the tags in cellForRowAtIndexPath or viewDidLoad, wherever you init the textFields.
Add the textFields to the cell's contentView, not the cell itself.
You don't have to resign first responder from the first text field, you can just becomeFirstResponder on the new one.
Make sure you're handling the last text view edge case: You could loop around to the first text field or simply dismiss the keyboard at the end.
If you want to get the textField in the cell:
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:
[NSIndexPath indexPathForRow:ROW_NUMBER inSection:1]];
UITextField *textField = (UITextField *)[cell.contentView viewWithTag:TEXT_FIELD_TAG];

Text entry ios question

I have two UILabels in a view that display my band and song name as strings. I am working on also adding the option to change either of these strings manually. I want to keep it as is, and I've added 2 buttons to manually enter a song name or band name. The thing is, all the text editing as far as I understand it needs to have actual TextField or TextView to bring up the keyboard etc.
I just want to touch one button for "enter song name" and be given a keyboard and when enter is hit, replace the string in the uilabel with that string, and the same for enter band name uibutton, and change the uilabel again.
Problem is in the docs I don't really understand how to do this. Does anyone have an idea about text entry in ios and can give me a pointer/tip on how to do this?
Just Declare your label Global and implement in implementation method
Take all song and all album data in array
and display all data in table view
now by using Delegate method of
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
change the text of Label as u want
#interface songViewController ()
{
UILabel *bandLabel;
UILabel *songLabel;
NSArray *bandData;
NSArray *songData;
}
#end
#implementation songCreateViewController
- (void)viewDidLoad {
[super viewDidLoad];
{
bandLabel=[[UILabel alloc]init];
bandLabel.frame=CGRectMake(40, 580, 500, 30);
bandLabel.text=#"Join group as";
[self.view addSubview:bandLabel];
songLabel=[[UILabel alloc]init];
songLabel.frame=CGRectMake(40, 580, 500, 30);
songLabel.text=#"Join group as";
[self.view addSubview:songLabel];
}
#pragma mark Table view Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView==albumTableView)
{
bandLabel.text=[bandData objectAtIndex:indexPath.row];
}
else
if(tableView==songTableView)
{
songLabel.text=[songData objectAtIndex:indexPath.row];
}
#end
What you are going to have to do is replace the label with a text field and set that as the first responder when the user presses the button. You will need a class that is the delegate of the text field so you handle enter in the textFieldShouldReturn method to resign first responder on the text field (to close the keyboard) and change the text field back to the label view and update its contents.
It can be achieve by many ways..
1) you can use hidden in your button methods to hide label and show textView.
2) you can use textField in place of label, make textfield.editable=NO and when button is pressed just clear the textfield by textfield.text=nil and make textfield editable.
Try these..
You want like when user touch on button you want to add text and button should be look like UILable. is it your objective?
If I am right you can achieve this thing to add gesture in uilable, you don't need to add button and handle tab method you can change the text.
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
myView.addGestureRecognizer(tap)
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("Hello World")
}

How to make a custom tableView cell accessory

I have not yet found any really good examples on how to do this. There is an image that I want to use as the accessory button and when I put it in and click on it doesn't work. So it looks correct but doesn't work...
Here is my code:
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
cell.accessoryView = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"TableView_Green_Disclosure.png"]];
So how do I get my UIImageView to call accessoryButtonTappedForRowWithIndexPath whenever it is tapped?
A thorough reading of accessoryView and accessoryType would reveal that they are mutually exclusive ways to customize a cell.
Setting the accessoryType will cause the table view delegate method to be called when it is tapped.
Setting the accessoryView will ignore the setting of accessoryType and give you something to display. If you want to receive a callback from the custom view you've put in place, it should be a control that is wired up to do so. (Or any view with a gesture recognizer.)
If you use a button, and set its action to accessoryTapped:, you will receive the button as the "sender" argument. You can walk up the view hierarchy until you find a table view cell, and then ask your table view what the indexPath of that cell is. This will then get you an index into your model objects and you be able to act on it appropriately.
Alternate to the button, you can enable interaction on the UIImageView above, and add a gesture recognizer to it.
To make the button actually do something, you'll need to implement - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath from UITableViewDelegate.
When an accessory button is tapped in a row, this method will be called and you'll have the chance to act appropriately using the passed in index path to determine which row's accessory was tapped.
Check the blog post hdr->cmdline for creating custom accessory view for UITableView.
The author used UIButton objects with images for custom accessory view.
To make use of the accessoryView - you would need to set the cell's accessoryType to UITableViewCellAccessoryNone deposit a UIButton (with associated image) into the cell and then wire it up to receive user touches. You might use something like the code below as the IBAction response to the cell's UIButton being touched:
- (IBAction) accessoryButtonPressed:(id) sender
{
NSUInteger pathInts[] = { 0,0 };
pathInts[1] = self.currentselectedrow; // ivar set when tableview row last selected
NSIndexPath* indexPath = [NSIndexPath indexPathWithIndexes:pathInts length:2];
[self tableView:mytableview accessoryButtonTappedForRowWithIndexPath:indexPath];
}
The UIButton would be wired to execute this glue code by way of a line inside your tableview's "cellForRowAtIndexPath:" function
[thecell setButtonTarget:self action:#selector(accessoryButtonPressed:)];
One thing I noticed is that the UIButton seems to want a 'swipe right' versus a simple 'tap' touch in order to trigger the event - but it could be my beta iOS that's the problem. Note that I had added a UIButton* object named 'cell_accessoryButton' to the Custom Cell source.
In the cell's source you'd support the 'setButtonTarget' call with code like this:
- (void) setButtonTarget:(MyViewController*)inTarget action:(SEL) inAction
{
[self.cell_accessoryButton addTarget: inTarget
action: (SEL) inAction
forControlEvents: UIControlEventTouchUpInside];
}
It's so much easier to just use the accessoryType reference and let iOS do the heavy lifting - but, if you want a custom graphic, etc - this is another path that works.

Changing an Object in UITableViewCell is also changing the Object of a reused UITableViewCell

Code updated to working version. Thanks again for the help :)
Hey guys. I have a UITableViewController set up to use a custom cell loaded from a nib. Here's my cellForRowAtIndexPath:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = #"PeopleFilterTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"PeopleFilterTableViewCell" owner:self options:nil];
cell = peopleFilterTableViewCell;
self.peopleFilterTableViewCell = nil;
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
PeopleFilterTableViewCell* tableViewCell = (PeopleFilterTableViewCell *) cell;
/* Set direct button name */
Person* personAtRow = [directsToShow objectAtIndex:indexPath.row];
[tableViewCell.directButton setTitle:personAtRow.name forState:UIControlStateNormal];
/* Set direct head count */
tableViewCell.headcountLabel.text = [NSString stringWithFormat:#"%d", personAtRow.totalHeadCount];
UIImage* unselectedImage = [UIImage imageNamed:#"filterButton.png"];
UIImage* selectedImage = [UIImage imageNamed:#"filterButtonClosed.png"];
UIButton* newFilterButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
/* Set filter button image */
if(personAtRow.filtered){
[newFilterButton setSelected:YES];
} else {
[newFilterButton setSelected:NO];
}
tableViewCell.filterButton = newFilterButton;
return cell;
}
This seems to work fine for me, but one issue has come up with the code after the /* set filter button image */ comment.
The filter button is a UIButton in my custom cell nib that is supposed to reflect the state of a model array containing 'Person' objects, which have a field that can be toggled to represent whether they are being filtered or not.
The way that I allow a user to update this model object is through a custom delegate method on my top level controller, which, whenever the user clicks the filter button, updates the model and the state of the button, and additionally updates a mapViewController with some data to show based on the state of the model:
- (void)updateViews:(id)sender {
UIImage* unselectedImage = [UIImage imageNamed:#"filterButton.png"];
UIImage* selectedImage = [UIImage imageNamed:#"filterButtonClosed.png"];
int row = [self rowOfCellSubView:sender];
Person* personToFilter = [self.directsToBeShown objectAtIndex:row];
NSLog(#"Filtering person with corpId: %#, name: %#", personToFilter.corpId, personToFilter.name);
if (personToFilter.filtered){
//update button image
[sender setImage:unselectedImage forState:UIControlStateNormal];
[sender setSelected:NO];
//add person back.
Person* directFiltered = [self.directsToBeShown objectAtIndex:row];
directFiltered.filtered = NO;
NSLog(#"Filtering person with corpId: %#, name: %#, filtered: %d", directFiltered.corpId, directFiltered.name, directFiltered.filtered);
} else {
//update button image
[sender setImage:selectedImage forState:UIControlStateSelected];
[sender setSelected:YES];
//remove person.
personToFilter.filtered = YES;
NSLog(#"Filtering person with corpId: %#, name: %#, filtered: %d", personToFilter.corpId, personToFilter.name, personToFilter.filtered);
}
[self updateSitesToShow];
[self.mapViewController performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:NO];
}
My issue comes with the updating of the state for the filter button. When my app loads the tableview, everything looks fine. When I click a filter button in a certain cell row the state of the button updates correctly, and my model objects are also updating correctly since I see the expected behavior from the mapView which I'm ultimately updating.
However, the issue is that when I click on the filterButton in one cell row and then scroll down a few rows, I notice another filter button in a different cell now has the same state as the one I clicked a few rows above. If I scroll back up again, the original button I clicked 'on' now seems to be 'off' but the row below it now appears 'on'. Of course all this is affecting is the actual display of the buttons. The actual state of the buttons is consistent and working correctly.
I know this issue must have something to do with the fact that my cells are being reused, and I'm guessing somehow the same buttons are being referenced for different cell rows. I'm at a loss as to how this is happening though, since I'm creating a new filter button for each cell, whether the cell is reused or not, and resetting the filterButton property of the cell to be the newly created object. Notice for example that the text of the headCount property, which is a UILabel also defined in the cell nib, is also being reassigned to a new String object for each cell, and it is displaying correctly for each row.
I've been struggling with this problem for a few days now. Any help or suggestions at all would be really appreciated.
Table views cache their cells to allow you to reuse them, which you're doing whenever you call dequeueReusableCellWithIdentifier:. You should call setSelected: before the end of your tableView:cellForRowAtIndexPath: method to synchronize the state of the button with the state of the Person instance that corresponds to the current row.
Another thing to consider is that creating new button instances each time you return a cell is pretty wasteful. Consider creating and configuring the buttons (setting titles, images, etc.) once per cell instance inside the if block where you're loading the cell from the nib file.
This is a typical issue that happens when you change the cell view structure after it has been dequeued, while you're allowed to change the cell structure only in the alloc/init stage. After dequeue or alloc/init you are allowed to customize the content only and not the structure.
In your case, when the cell (let's say it is row-0) is loaded from the nib, the internal subviews structure is created (as defined in the Nib) and filterButton instance is assigned to one of these subviews. But a few lines below you create a new UIButton and replace the filterButton instance with this new one, but the real button subview will remain the same! Now when you click a button, of course the "real" button (that is the button in the cell view hierarchy which has been originally created by the Nib) will be triggered, the callback called and the state changed.
Later, when you scroll up this row-0 cell, it is removed from screen and enqued and then re-used for another cell, let's say row-9. At this point, former cell row-0 is going to be reused for cell row-9, but setting filterButton has still no effect, as you're keep going using the original button loaded initially by the Nib for cell row-0. Of course you will see these buttons states to be messed during scrollings as they are reused by the queue mechanism differently each time (so row-0 --> row-9, later row-0 --> row-8 and so on).
The solution is simply to change the button status: [self.filterButton setSelected:NO|YES] and not change the cell view content.
So the golden rule is: NEVER change the cell structure after you've dequed it. If you need to change the structure then use DIFFERENT cell IDs. Of course the more customizable is the cell the easier is the possibility to reuse them.