Automatic scroll to the next cell in a PFQueryTableViewController in Swift - ios

I have a PFTableViewController with PFTableViewCells in Swift.
In each TableViewCell (with a height of 80% the screen size), there is a button. I would like that when a user select the button, there is an automatic scroll to the next TableViewCell.
Any idea how I can make it?
Thanks a lot

Just determine the indexPath and scroll to the next indexPath. This example assumes a flat table without sections.
func buttonTapped(sender: UIButton) {
let point = sender.convertPoint(CGPointZero, toView:tableView)
let indexPath = tableView.indexPathForRowAtPoint(point)!
// check for last item in table
if indexPath.row < tableView.numberOfRowsInSection(indexPath.section) {
let newIndexPath = NSIndexPath(forRow:indexPath.row + 1 inSection:indexPath.section)
tableView.scrollToRowAtIndexPath(
newIndexPath, atScrollPosition:.Top, animated: true)
}
}

Related

UITableView Scrolls automatically while textfield begins editing

I am developing an iOS app which has different forms which is populated into a UITableview based on users selection. Each form has different fields like Textfield, DatePicker, PickerView. So I used a single TableViewCell (nib) to hold all these and show or hide the items based on question.
There is save function defined which will save values when user enters to an array.
My issue is, at times my tableview scrolls as if the index goes out of control. like when I select any textfield, Tableview scrolls to top. I have removed all keyboard observer methods and checked, still it is happening.
Below is my save function code:
func saveFormValue(mystr: String) {
//selectedIndex is a global variable (NSIndexPath)
let dict = sections[selectedIndex!.section].questions![selectedIndex!.row]
dict.answer = mystr
sections[selectedIndex!.section].questions![selectedIndex!.row] = dict
let answer = updated_answer.init(ID: ID, form_id: selected_form_id, form_name: formName, section_id: dict.section_id ?? "",question_id: dict.question_id ?? "", question_name: dict.question_name!,question_type:dict.question_type!)
updatedArray.append(answer)
self.tableView.reloadData()
}
This is the code in textfieldDidBeginEditing function (how selectedIndexPath is initialized):
guard let index = tableView.indexPath(for: cell) else {
return
}
selectedIndex = index as NSIndexPath
I have added delegate for cell, and one thing I noticed is, this issue is happening whenever I press pickerview or datepicker once. I couldn't see this issue If I only touch textField cells only.
Please let me know for any further details.
Try this code hope this helps to you.
if let thisIndexPath = tableView.indexPath(for: cell) {
tableView.scrollToRow(at: thisIndexPath, at: .top, animated: false)
}
On textfield delegate method textFieldDidBeginEditing use the following code:
func textFieldDidBeginEditing(_ textField: UITextField) {
let indexParh = NSIndexPath(row: textField.tag, section: 0)
self.constTBL_Bottom.constant = 260
self.tblViewObj.scrollToRow(at: indexParh as IndexPath, at: .middle, animated: false)
}
Also you need to manage the table bottom constant. When you resigning your keyboard set table view constant to 0
Hope this will work :)

how can I refresh a single button in UITableViewCell instead of refreshing whole table or whole cell?

In my swift app I have a UITableView with one static cell and many dynamic cells.
Static cell contains different fields, such as labels, map (taken from MapKit) and a button, that indicates whether user voted up or not.
Now, when user presses the button, I want to change its color, possibly without refreshing anything else.
So far my code looks like this:
var currentUserVote:Int = 0
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).row == 0 {
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
fetchScore(cell.score)
let voteUpImage = UIImage(named: "voteUp");
let tintedVoteUpImage = voteUpImage?.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
cell.voteUpButton.setImage(tintedVoteUpImage, for: UIControlState())
checkUsersVote() { responseObject in
if(responseObject == 1) {
cell.voteUpButton.tintColor = orangeColor
} else if (responseObject == -1){
cell.voteUpButton.tintColor = greyColor
} else {
cell.voteUpButton.tintColor = greyColor
}
self.currentUserVote = responseObject
}
//map handling:
let regionRadius: CLLocationDistance = 1000
let initialLocation = CLLocation(latitude: latitude, longitude: longitude)
centerMapOnLocation(initialLocation, map: cell.mapView, radius: regionRadius)
//cell.mapView.isScrollEnabled = false
cell.mapView.delegate = self
.
.
.
return cell
} else {
//handle dynamic cells
}
}
So in the method above I'm checking if user voted already and based on that I'm setting different color on the button. I'm also centering the map on a specific point.
Now, since it's a static cell, I connected IBAction outlet to that button:
#IBAction func voteUpButtonAction(_ sender: AnyObject) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
}
and the vote method works as follows:
func vote(_ vote: Int){
let indexPath = IndexPath(row: 0, section: 0)
let cell = tview.dequeueReusableCell(withIdentifier: "cellStatic") as! VideoDetailsCell
switch(vote) {
case 1:
cell.voteUpButton.tintColor = orangeColor
case 0:
cell.voteUpButton.tintColor = greyColor
case -1:
cell.voteUpButton.tintColor = greyColor
default:
cell.voteUpButton.tintColor = greyColor
}
tview.beginUpdates()
tview.reloadRows(at: [indexPath], with: .automatic)
tview.endUpdates()
currentUserVote = vote
//sending vote to my backend
}
My problem is, that when user taps the button, he invokes the method vote, then - based on the vote, the button changes color, but immediately after that method cellForRow is called and it changes the color of the button again. Also, it refreshes the map that's inside of it.
What I want to achieve is that when user taps the button, it should immediately change its color and that's it. Map stays untouched and the button is not changed again from cellForRow.
Is there a way of refreshing only that particular button without calling again cellForRow?
First of all, you confuse static and dynamic cells. You can use static cells only in the UITableViewController and you can't use static and dynamic cell at the same time.
I strongly recommend you not to use cell for storing map and button. All elements from the cell will be released after scrolling it beyond the screen.
I can advise you use TableViewHeaderView for this task. In this case you will be able set button and map view as #IBOutlet.
(See more about adding tableview headerView. You can also set it from interface builder.)
Another way is change tableView.contentInset and set your view with map and button as subview to tableView. This method is used when you need create Stretchy Headers.
It should be quite easy, simply do it in your button handler. The sender argument there is the button object that caused the action. When you were connecting it from IB there was a dropdown to select sender type, you may have missed it and the whole thing would have been obvious with UIButton type there.
Simply change your handler like this :
#IBAction func voteUpButtonAction(_ sender: UIButton) {
if(currentUserVote == 1) {
self.vote(0)
}else if (currentUserVote == -1){
self.vote(1)
} else {
self.vote(1)
}
sender.backgroundColor = yourFavouriteColor
}
Another approach would be to create an IBOutlet for your button, since its from a static cell, and then you would be able to reference it from any place in your view controller.
In this call:
func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I see it calls checkUsersVote() which I'm guessing should get the updated value set in the vote() call. Could the problem be that you aren't doing this
currentUserVote = vote
until after reloadRows() is called?

Force touch on UICollectionView inside of UITableViewCell

I have a viewController with a Tableview, multiple TableViewCells and in each TableViewCell, a UICollectionView with multiple UICollectionViewItems. Each collectionView item has a label and image view. I'm trying to get 3d touch to work so that the user and peek and pop by force touching on areas of the tableCell that don't contain the collection view, to preview and pop into one view controller and then be able to do the same thing with one of the images in the collectionView but preview and pop into a different view controller. I have the first scenario working fine, the tableCell remains sharp on the screen when starting to force touch and "peek". I'm stuck on getting this to work in the collection view, no matter what I do only an image view frame remains sharp on the first tableview row regardless of which row i'm actually pressing. Code below:
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
//get the tableviewCell
if let tableCellPath = tableView.indexPathForRow(at: location) {
print("tableCellPath=", tableCellPath)
if let tableCell = tableView.cellForRow(at: tableCellPath) as? VenueMainTableViewCell {
//user tapped on a beer in the collectionview
if let collectionView = tableCell.collectionView {
let collectionPoint = collectionView.convert(location, from: tableView)
if let cvPath = collectionView.indexPathForItem(at: collectionPoint) {
let collectionViewCell = collectionView.cellForItem(at: cvPath) as? VenueMainCollectionViewCell
let cvFrame = collectionViewCell?.itemLabelImageView.frame
let bc = storyboard?.instantiateViewController(withIdentifier: "itemDetail") as! ItemDetailViewController
let ven = UTcheckin.sharedInstance.Venues[collectionView.tag]
let selectedItem = ven.ItemsAtVenue[(collectionViewCell?.tag)!]
bc.item = selectedItem
previewingContext.sourceRect = cvFrame!
return bc
}
}
}
if let tablePath = tableView.indexPathForRow(at: location) {
//user tapping on a venue, this works
previewingContext.sourceRect = tableView.rectForRow(at: tablePath)
let vc = storyboard?.instantiateViewController(withIdentifier: "venueDetail") as! VenueDetailViewController
vc.venue = UTcheckin.sharedInstance.Venues[tablePath.row]
return vc
}
return nil
}
return nil
}
It seems like I need to get the rect of the collection view item image view but how can I access this since it is in the table cell? Thanks in advance for any pointers.
I think the solution for this is the same as for UITableView. You have to register each cell for previewing using registerForPreviewingWithDelegate method. You should register it in
cellForRow method.
This should be very helpful for you. Especially The Solution Paragraph:
How to Peek & Pop A Specific View Inside a UITableViewCell

Is it possible to derive the indexPath if you know the row?

Okay, I'm going to try to break this down as simply as I am able. I have a tableView in a ViewController. I have two prototype cells for the table. I am reusing the cells multiple times to populate the table.
In one of the cells, I've added gesture recognizer to the label through which I'm making a textField visible on place of the label and hiding the label. Now I want the labels text to change to what I've entered in the textField when I'm done using the textField and hit the return key. So i implemented the UITextFieldDelegate protocol in the viewController. I've also added tags to each of the textFields in the cell so that I know what textField is returning and what row the textField is in.
Basically, what I want to know is if there is any way to get the indexPath if I already know the indexPath.row?
For the gesture recognizers, i was able to work around this issue by getting the indexPath from the tapped location:
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
let indexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(indexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
I need the indexPath so that I can refer to the elements contained within a cell. Can anyone offer any insights? Has anybody tried a similar approach? Is there any way I can access the cell by just using the row?
If you are able to get the indexPath inside the GestureMethod then you can create one instance property of type NSIndexPath store its value inside that Gesture's method and later used the indexPath inside textFieldShouldReturn delegate method, something like this.
var selectedIndexPath: NSIndexPath?
func genderTapped(sender: UITapGestureRecognizer) {
let tapLocation = sender.locationInView(self.profileInfoTable)
self.selectedIndexPath = self.profileInfoTable.indexPathForRowAtPoint(tapLocation)
let cell = self.profileInfoTable.cellForRowAtIndexPath(self.selectedIndexPath!) as! editUserDataCell
cell.savedUserInput.hidden = true
cell.userDetailTextfield.becomeFirstResponder()
cell.userDetailTextfield.hidden = false
cell.userDetailTextfield.text = cell.savedUserInput.text!
}
Now use this self.selectedIndexPath inside UITextFieldDelegate method.
Edit: From your question's comment you have told that you have just one Section so you can also create indexPath from that textField's tag this way.
func textFieldShouldReturn(textField: UITextField) -> Bool {
let indexPath = NSIndexPath(forRow: textField.tag, inSection: 0)
//Or You can use self.selectedIndexPath also
}
In case of single or multiple sections, the below code will work
In your cellForRowAtIndexPath, set the tag as below:-
let tag = indexPath.section*100 + indexPath.row
cell.savedUserInput.tag = tag
cell.userDetailTextfield.tag = tag
In your textfield delegate method, get the indexPath as follows:-
func genderTapped(sender: UITapGestureRecognizer) {
let textfieldObject = sender as! UITextField
let sectionTag = textfieldObject.tag % 100
let rowTag = textfieldObject.tag / 100
let indexPath = NSIndexPath(forRow: rowTag.tag, inSection: sectionTag)
}
Disclaimer: This is not an answer to the literal question asked here, but it might provide an simpler solution to OP's goal.
Unless you need to do something in addition to what you described in your question it seems to me that a much easier solution would be not to use labels at all but in stead just use an UITextField and set it's enabled property to false when you want it to act like an label.
You can subclass the UITextField if you need the styling to change when the mode changes.
If you know the row number which you are accessing and the section in which the row is, then use this code
let indexPath = NSIndexPath(forRow: row, inSection: section)
For accessing the cell corresponding to this indexPath, use
let cell = tableView.cellForRowAtIndexPath(indexPath) as! tableViewCell

UITableView mess with the rows content

I tried to copy only the necessary code to show my problem. I have a tableview with dynamic content. I created a prototype cell and it has a user name and 10 stars (it's a rating page). People in the group are allowed to rate other people. Everything is working ok, but I have a problem when I scroll down. If I rate my first user with 8 stars, when I scroll down then some user that was in the bottom area of the tableview, appears with the rate that I gave to my first user. I know that tableview reuse cells. I tried many things but with no success. Hope someone can help me on that.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let model = users[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("RatingCell") as! RatingTableViewCell
cell.tag = indexPath.row
cell.playerLabel.text = model.name
cell.averageView.layer.borderWidth = 1
cell.averageView.layer.borderColor = Color.Gray1.CGColor
cell.averageView.layer.cornerRadius = 5
cell.starsView.userInteractionEnabled = true
cell.averageLabel.text = "\(user.grade)"
for i in 0...9 {
let star = cell.starsView.subviews[i] as! UIImageView
star.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(starTap)))
star.userInteractionEnabled = true
star.tag = i
star.image = UIImage(named: (i + 1 <= grade ? "star-selected" : "star-empty"))
}
return cell
}
func changeRating(sender: UIImageView) {
let selectedStarIndex = sender.tag
let cell = sender.superview?.superview?.superview as! RatingTableViewCell
let model = users[cell.tag]
let stars = sender.superview?.subviews as! [UIImageView]
cell.averageLabel.text = "\(selectedStarIndex + 1)"
for i in 0...9 {
let imgName = i <= selectedStarIndex ? "star-selected" : "star-empty"
stars[i].image = UIImage(named: imgName)
}
}
func starTap(gesture: UITapGestureRecognizer) {
changeRating(gesture.view as! UIImageView)
}
The way to solve this problem is by updating the model that holds all the information for the uitableviewcell. Whenever a rating is updated fora particular cell, make sure you reflect that update in the respective object / dictionary in an array. Furthermore, if you have a customuitableviewcell, it might be a good idea to reset the stars in the "prepareForUse" function, so that way when a cell is reused it doesn't use old data.
In your comments, you said that you have an array with selected rates.But you did not show that in your code.
In my opinion, you need record indexPath too, because indexPath.row is binding with your rate data(may be grade?).The best way to do so is that #Jay described up.And you should not write the code of configuring cell data and cell's logic in your view controller.If your business logic is complex, you will find that it is a nightmare.^=^

Resources