I have seen posts where it is suggested to return nil in willSelectRowAtIndexPath, if didSelectRowAtIndex is to be disabled while editing UITableViewCell.
But my question goes around the tapping the cell first, and then without releasing the touch, begin and end the edit using swipe.
Ideally, willSelectRowAtIndexPath will block didSelectRowAtIndex once the cell is in editing mode. But in this case, didSelectRowAtIndex is first fired and without releasing the touch, cell begins and end editing without any action.
In this case, on ending the edit (after didEndEditingRowAtIndexPath), didSelectRowAtIndex gets fired. How can I avoid this ?
I created custom cell and added some logic to see whether cell was editing previously before selecting it, it worked for me!
Here is code:
Table View Methods-
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
if cell.cellWasEditing() {
//this is main logic
return nil
}
return indexPath
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
Custom Cell-
#IBOutlet weak var _customLbl: UILabel!
private var prevEditingState = UITableViewCellStateMask.DefaultMask
private var wasEdititng = false
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override func didTransitionToState(state: UITableViewCellStateMask) {
if prevEditingState == UITableViewCellStateMask.ShowingEditControlMask || prevEditingState == UITableViewCellStateMask.ShowingDeleteConfirmationMask {
wasEdititng = true
}
else {
wasEdititng = false
}
prevEditingState = state
}
//MARK: Public Methods
func setCellIndexLabel(index: Int) {
_customLbl!.text = String(index)
}
func cellWasEditing() -> Bool {
if wasEdititng {
wasEdititng = false
return true
}
return false
}
I hope this will solve your problem!!
You can try to use custom cell, and add a UIPanGestureRecognizer on it.
Then monitor the gesture state, and do what you want when the state is end.
Related
This question already has answers here:
Open UITableView edit action buttons programmatically
(5 answers)
Closed 5 years ago.
I have made a few custom edit actions on my tableviewcell. It works fine when I swipe, but I was wondering if there was any way to trigger these actions when I tap the cell. Also, I have seen lots of people answer similar questions with just,
tableView.setEditing(true, animated: true)
though this is not the solution I am looking for. I want the actions to immediately get displayed without the press of another button.
Short answer is - there is no such way.
However, if you really need something like that, you can mimic this behaviour, though it requires lot more implementation and state handling on your own.
Here is a quick and very dirty solution, which overrides touchesEnded method of your custom cell. Remember to set up Cell as a dynamic prototype of the cell in your table view in relevant storyboard and set its reuse identifier to identitifer.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CellDelegate {
#IBOutlet weak var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "identifier") as? Cell else {
return UITableViewCell()
}
cell.textLabel?.text = "\(indexPath.row)"
cell.indexPath = indexPath
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
return nil
}
func doAction(for cell: Cell) {
let indexPath = cell.indexPath
print("doing action for cell at: \(indexPath!.row)")
// your implementation for action
// maybe delete a cell or whatever?
cell.hideFakeActions()
}
}
protocol CellDelegate: class {
func doAction(for cell: Cell)
}
class Cell: UITableViewCell {
weak var delegate: CellDelegate?
var indexPath: IndexPath!
#IBOutlet weak var buttonAction1: UIButton?
#IBOutlet weak var constraintButtonFakeActionWidth: NSLayoutConstraint?
override func awakeFromNib() {
self.constraintButtonFakeActionWidth?.constant = 0
}
override func touchesEnded(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else {
return
}
if self.point(inside: point, with: event) {
print("event is in cell number \(indexPath.row)")
self.showFakeActions()
}
}
func showFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 80
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
func hideFakeActions() {
self.constraintButtonFakeActionWidth?.constant = 0
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
}
}
#IBAction func fakeAction() {
delegate?.doAction(for: self)
}
}
So how does it work? Each cell is a UIView which inherits from abstract UIResponder interface. You can override its methods to do actions on your own on behalf of events that are dispatched to them. This is the first step where you override touchesEnded.
Take a look at the screenshot from interface builder - you have to hook up the constraint.
I've also implemented the delegate which returns nil for all edit actions of the cells, so they don't interfere with your workaround.
Next, you set up a custom delegate to get a callback from the cell. I also attached IndexPath to the cell for the convenience of managing data in the dataSource, which you have to implement.
Remember that this code lacks a lot, like prepareForReuse method implementation. Also, you probably want to do additional checks in touchesEnded override which would guarantee that this delegate callback is not fired more than once per touch and prevent multiple touches. The logic for disabling user interaction on a cell is not implemented here as well. And interface requires fine-tuning (like text appears to be squashed during the animation).
I have in my iOS app a mapView, and a UISearchBar to search locations and center the user on it. The results of my search are made by autocompletion and displayed in a tableView.
I had trouble with it, when i clicked on cancel, it displayed me all of my datas in the tableView.
So I implemented this function:
func searchBarCancelButtonClicked(searchBar: UISearchBar) {
searchTableView.hidden = true
}
But the render of that function makes this:
I don't know (and don't know how to see) if my mapView is still under the black screen.
By the way, if I delete my cancel button function, and search something, if I click on Cancel it will display something like this:
To hide my tableView again, I have to make another search, than delete it with the little cross button, then cancel.
My code, of the autocompletion search, is like that:
func initTableView() {
searchTableView.delegate = self
searchTableView.dataSource = self
searchTableView.scrollEnabled = true
searchTableView.hidden = true
}
func searchBarSearchButtonClicked() {
searchTableView.hidden = false
searchBar.resignFirstResponder()
dismissViewControllerAnimated(true, completion: nil)
searchBar(searchBar, textDidChange: searchBar.text!)
}
func searchBar(_searchBar: UISearchBar, textDidChange searchText: String) {
searchTableView.reloadData()
searchAutocompleteEntriesWithSubstring(searchText)
}
func searchAutocompleteEntriesWithSubstring(substring: String) {
searchBar.filtredDatas.removeAll(keepCapacity: false)
for curString in searchBar.datas {
let myString:NSString! = curString.title! as NSString
let substringRange :NSRange! = myString.rangeOfString(substring)
if (substringRange.location == 0) {
searchBar.filtredDatas.append(curString)
}
}
}
This is from my ViewController.swift file.
My searchBar variable is a custom class that contains an NSArray of datas and another of fileterDatas.
#IBOutlet weak var mapView: UGOMapController!
#IBOutlet var searchTableView: UITableView!
#IBOutlet weak var searchBar: UGOSearchBar!
Thanks for your help.
If you need more of my code, I can edit this post.
Edit:
Here are my UITableView functions, used for searchTableView
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchBar.filtredDatas.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let autoCompleteRowIdentifier = "AutoComplete"
var cell = tableView.dequeueReusableCellWithIdentifier(autoCompleteRowIdentifier) as UITableViewCell!
if cell != nil {
let index = indexPath.row as Int
if (searchBar.filtredDatas.count > 0) {
cell!.textLabel!.text = searchBar.filtredDatas[index].title
}
}
else {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: autoCompleteRowIdentifier)
}
return cell!
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
searchBar.text = selectedCell.textLabel!.text
}
And my hierarchy:
And my viewController inspector:
You have 3 different outlets connected to the table view.
It means that when you hide the tableView, you also hide the view, because it's connected to the same Search Table View
You have to make sure that you connect your table view just to one outlet.
Edit
To be more precise:
You have to change your view controller from UITableViewController to UIViewController.
The view outlet will be connected to viewController's view property.
The tableView outlet will be removed, because in a normal UIViewController this property doesn't exist. (It's defined in UITableViewController)
SearchTableView should be connected to the searchTableView property that you define in your view controller.
I have a TableViewController (lets call TVC1) with a row that says "OD" (which stands for Outer Diameter).
Upon selecting this row, a bunch of rows in a new TableViewController (lets call TVC2) containing the various OD (casingOD in my code) shows. What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection. My code for this currently fails...Could anyone help point me in the right direction? If you require TVC1 code i'll happily post it, i'm just trying to save any unneccessary code reading for you folks :)
My TVC2 code is as follows:
import UIKit
class CasingSelectionTableViewController: UITableViewController {
var selectedData: Data?
let casingOD = ["114.3", "127.0", "139.7", "168.3" , "177.8", "193.7", "219.1", "244.5", "247.6", "273.1", "298.4", "298.4", "339.7", "406.4", "473.0", "508"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
switch selectedData! {
case .OuterDiameter:
print(casingOD)
case .Weight:
print(casingWeight114) // I deleted the casingWeight114 line of code as its not required for this question
case .InnerDiameter:
print(id114) // I deleted the id114 line as its not required for this question
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return casingOD.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var casingSpec: UITableViewCell!
if selectedData == Data.OuterDiameter {
casingSpec = tableView.dequeueReusableCellWithIdentifier("selectedCasingSpec", forIndexPath: indexPath)
let casingODSpec = casingOD[indexPath.row]
casingSpec.textLabel?.text = casingODSpec
return casingSpec
} else {
return casingSpec
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selection: UITableViewCell!
selection.textLabel?.text = indexPath.row as! String
}
What I want to happen is when the user selects the OD it will segue back to the main TableViewController with the string that corresponds to the user selection.
First of all you'll need to implement a way for TVC2 to notify TVC1 that a value has been selected.
A common way to do such thing is by using delegation. You can define a delegate protocol like this:
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
Then add a var delegate: TVC2Delegate? property to TVC2.
You'll then make TVC1 comform to TVC2Delegate by implementing that method in TVC1.
When presenting TVC2 from TVC1 remember to set it as the delegate for TVC2.
// In TVC1
tvc2.delegate = self
To connect TVC1 and TVC2 you could add a bit o logic to your tableView(tableView:,didSelectRowAtIndexPath:) method call the delegate with the selected value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = indexPath.row as! String
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
Finally, in TVC1's implementation of the delegate method you can take care of dismissing TVC2 if needed.
Update:
This is how the final implementation of these bits might look like:
// In TVC1
class TVC1: UITableViewController, TVC2Delegate {
// ...
// Implement the method(s) of TVC2Delegate
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String) {
// Do whatever you need to do with the outerDiameter parameter
}
}
// In TVC2
protocol TVC2Delegate {
func tvc2(tvc2: TVC2, didSelectOuterDiameter outerDiameter: String)
}
class CasingSelectionTableViewController: UITableViewController {
var delegate: TVC2Delegate?
// ...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let stringValue = casingOD[indexPath.row]
// Do anything you need to do related to TVC2 here.
// Then finally
delegate?.tvc2(self, didSelectOuterDiameter: stringValue)
}
}
Use the delegate approach as suggested in the answer by #Mokagio. And in case you're having issue in getting the string, here is the answer
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! UITableViewCell
let stringValue = cell.textLabel.text //You can get this from your datasource as well)
//call the delegate
}
This question already has an answer here:
Table view didSelectRowAtIndexPath, only works after selecting second click
(1 answer)
Closed 7 years ago.
So here's the thing:
I implemented a UITableViewController with a custom cell..
Then i replaced the automatic segue cell to viewcontroller (storyboard) with viewcontroller to viewcontroller.
Code from my UITableViewController:
// MARK: - Tableview managment
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var lobbycell = tableView.dequeueReusableCellWithIdentifier("lobbycell") as! LobbyTableViewCell
let row = indexPath.row
if let channel = lobbies[row].lobbyName {
lobbycell.lobbynameLabel.text = channel
}
if let usercount = lobbies[row].users?.count {
lobbycell.usercountLabel.text = "Anzahl Spieler: \(usercount)"
}
if let host = lobbies[row].host?.username {
lobbycell.hostnameLabel.text = "Host: \(host)"
}
return lobbycell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lobbies.count
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let selectedLobby = lobbies[indexPath.row]
var loading = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
PFLobby.joinLobbyInBackgroundWithBlock(selectedLobby, user: PFUser.currentUser()!) { (success, error) -> Void in
loading.hide(true)
if success {
self.performSegueWithIdentifier("joinLobby", sender: self)
} else {
}
}
}
Now here's the problem:
If I click on a cell it's going to get gray (like selected) but won't call the didDeselectRowAtIndexPathfunc..
Only If I click on another cell it's going to call the didDeselectRowAtIndexPathwith the gray (like selected) cell's indexPath.
Are you confused with didSelectRowAtIndexPath and didDeselectRowAtIndexPath?
The latter will only be called if it's deselected, hence why it's called after you select another cell.
If you want to call the didDeselectRowAtIndexPath, then add the method deselectRowAtIndexPath in didSelectRowAtIndexPath and it will deselect right way.
This is expected behavior. tableView(_:didDeselectRowAtIndexPath:) will only be called after another cell is selected.
If you want, it is possible to intercept the selection event in tableView(:_willSelectRowAtIndexPath:):
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
if let selectedIndex = tableView.indexPathForSelectedRow() where selectedIndex == indexPath {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
return nil
}
return indexPath
}
The index path returned by this method will be selected. If you return nil, nothing will get selected.
I have an iOS application that needs to turn on/off lights remotely. The app gets the data for the lights from parse.com and builds a tableview with every individual cell showing the name of the light and a UISwitch. I want to know how to change the boolean value stored on parse.com when I switch on or off one of the lights. The problem is that the IBAction used by the switch is not boolean and I cannot write and if statement that updates the value of the light is the switch is pressed. I have created the IBaction in my cell class and hoped that could be used by the tableviewcontroller class.
Here is part of my tableviewcontroller class
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:RelayCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as RelayCell
let label:PFObject = self.labelArray.objectAtIndex(indexPath.row) as PFObject //create the object label
cell.relayTextField.text = label.objectForKey("text") as String //put the text in the labeltextField
if (label.objectForKey("switch") as NSObject == 1) {
//cell.mySwitch = true //turn the switch on depending on the boolean value in switchColumn
cell.mySwitch.setOn(true, animated: true)
}
else{
//cell.mySwitch = false //turn the switch on depending on the boolean value in switchColumn
cell.mySwitch.setOn(false, animated: true)
}
return cell
}
This code shows the state of each independent switch however, what I want now is to be able to press on each independent button on the app and change the value on the online database.
Could you help me out since I haven't found anything online.
class RelayCell: UITableViewCell {
#IBOutlet weak var mySwitch: UISwitch!
#IBOutlet weak var relayTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
relayTextField.layer.borderColor = UIColor.blackColor().CGColor
relayTextField.layer.borderWidth = 0.8
relayTextField.layer.cornerRadius = 10
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func switchChangedState(sender: UISwitch) {
}
}
This is my RelayCell class that is used by the tableView method in the tableViewController class.
One way to handle this is to add a callback property to RelayCell and to call the callback from switchChangedState:
class RelayCell: UITableViewCell {
typealias SwitchCallback = (Bool) -> Void
var switchCallback: SwitchCallback?
#IBAction func switchChangedState(sender: UISwitch) {
switchCallback?(sender.on)
}
// ... rest of RelayCell
}
In your tableView:cellForRowAtIndexPath: method, set the cell's callback:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:RelayCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as RelayCell
cell.switchCallback = { [weak self] (switchIsOn) in
self?.setSwitchValue(switchIsOn, forRowAtIndexPath:indexPath)
Void()
}
// ... rest of tableView:cellForRowAtIndexPath:
return cell
}
Then you can do whatever you need in setSwitchValue:forRowAtIndexPath:, which is a method you add to your table view controller class:
private func setSwitchValue(switchIsOn: Bool, forRowAtIndexPath indexPath: NSIndexPath) {
println("row \(indexPath.row) switch on-ness is now \(switchIsOn)")
}
UISwitch has "on" property, use it to get the boolean value for current state.