I took my lead from this tutorial: https://kylebashour.com/posts/context-menu-guide
I thought I had everything, yet when I click on the row, nothing happens
UPDATE: Thanks a comment I learned that contextMenuConfigurationForRowAt is called on a long press. Is there a way to call this on a click? I wanted to give users several options for contacting the people listed. But buttons in UITableViewCell doesn't seem to work, so a context menu is the best I can think of, but a long press isn't obvious for interacting with this table view.
import UIKit
class PeerSupportVC: UIViewController {
#IBOutlet weak var peerSupportTableView: UITableView!
var supportArray: NSArray = [];
fileprivate var configuration = Configuration.sharedInstance;
override func viewDidLoad() {
super.viewDidLoad()
self.peerSupportTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.peerSupportTableView.delegate = self;
self.peerSupportTableView.dataSource = self;
self.peerSupportTableView.rowHeight = 200.0
getPeerSupport();
}
func getPeerSupport(){
self.supportArray = ["One","Two","Three"]
DispatchQueue.main.async(execute: {
self.peerSupportTableView.reloadData()
});
}
}
extension PeerSupportVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let item = self.supportArray[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let phoneAction = UIAction(title: "Phone Call", image: UIImage(systemName: "person.fill")) { (action) in
DispatchQueue.main.async(execute: {
print("Wants to Phone Call");
});
}
let textAction = UIAction(title: "Text Messsage", image: UIImage(systemName: "person.badge.plus")) { (action) in
DispatchQueue.main.async(execute: {
print("Wants to Text Message");
});
}
return UIMenu(title: "Contact Options", children: [phoneAction, textAction])
}
}
}
extension PeerSupportVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Count of Support Array:",self.supportArray.count);
return self.supportArray.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath);
for viewToRemove in cell.subviews {
viewToRemove.removeFromSuperview();
}
let width = self.peerSupportTableView.frame.width;
if let result = self.supportArray[indexPath.row] as? String{
print(result);
let NameLabel = UILabel(frame: CGRect(x: 180, y: 0, width: width-155, height: 30));
NameLabel.textColor = .black;
NameLabel.text = " "+(result);
NameLabel.font = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight(rawValue: 400));
NameLabel.adjustsFontSizeToFitWidth = true;
cell.addSubview(NameLabel);
}
return cell;
}
}
I dumbed the code down to share, but this lesser version still doesn't work.
What I want to happen is a menu to pop up on the line that was selected and give the user options on how to contact the person they selected. Any help on displaying the menu would be greatly appreciated.
For reference, here's your code, modified to use a reusable cell:
// simple cell class, based on your code
class PeerCell: UITableViewCell {
let nameLabel = UILabel()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.font = .systemFont(ofSize: 12, weight: UIFont.Weight(rawValue: 400))
contentView.addSubview(nameLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: g.topAnchor),
nameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 180.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
nameLabel.heightAnchor.constraint(equalToConstant: 30.0),
])
}
}
class PeerSupportVC: UIViewController {
#IBOutlet var peerSupportTableView: UITableView!
var supportArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// register our custom cell
self.peerSupportTableView.register(PeerCell.self, forCellReuseIdentifier: "Cell")
self.peerSupportTableView.delegate = self;
self.peerSupportTableView.dataSource = self;
self.peerSupportTableView.rowHeight = 200.0
getPeerSupport();
}
func getPeerSupport(){
self.supportArray = ["One","Two","Three"]
}
}
extension PeerSupportVC: UITableViewDelegate {
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
let item = self.supportArray[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { suggestedActions in
let phoneAction = UIAction(title: "Phone Call", image: UIImage(systemName: "person.fill")) { (action) in
print("Wants to Phone Call");
}
let textAction = UIAction(title: "Text Messsage", image: UIImage(systemName: "person.badge.plus")) { (action) in
print("Wants to Text Message");
}
return UIMenu(title: "Contact Options", children: [phoneAction, textAction])
}
}
}
extension PeerSupportVC: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Count of Support Array:",self.supportArray.count);
return self.supportArray.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PeerCell
let result = self.supportArray[indexPath.row]
print(result)
cell.nameLabel.text = result
return cell;
}
}
As I said in my comment, though, your contextMenuConfigurationForRowAt was working as-is -- just need to long-press on the cell to trigger the call.
Related
I have 2 ViewControllers and each ViewController have a UITableView.
In the MainViewController I have few rows and I want to add for each row different Tags from the second ViewController.
My tags are saved in a Dictionary (I don't know if is the best way but I was thinking that maybe I will avoid to append a tag twice using a Dict instead of Array).
The problem is that I don't append correctly the selected tags and I don't know how I should do it.
Here I've created a small project which reflect my issue: https://github.com/tygruletz/AppendTagsToCells
Here is the code for Main VC:
class ChecklistVC: UIViewController {
#IBOutlet weak var questionsTableView: UITableView!
//Properties
lazy var itemSections: [ChecklistItemSection] = {
return ChecklistItemSection.checklistItemSections()
}()
var lastIndexPath: IndexPath!
var selectedIndexPath: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
questionsTableView.reloadData()
}
}
extension ChecklistVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itemCategory = itemSections[section]
return itemCategory.checklistItems.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return itemSections.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "checklistCell", for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
cell.delegate = self
cell.configCell(item)
cell.vehicleCommentLabel.text = item.vehicleComment
cell.trailerCommentLabel.text = item.trailerComment
cell.tagNameLabel.text = item.vehicleTags[indexPath.row]?.name
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goChecklistAddComment" {
let addCommentVC = segue.destination as! ChecklistAddCommentVC
addCommentVC.delegate = self
}
if segue.identifier == "goChecklistAddTag" {
let checklistAddTag = segue.destination as! ChecklistAddTagVC
checklistAddTag.indexForSelectedRow = self.selectedIndexPath
checklistAddTag.tagsCallback = { result in
print("result: \(result)")
let item = self.itemSections[self.lastIndexPath.section].checklistItems[self.lastIndexPath.row]
item.vehicleTags = result
}
}
}
}
Here is the code for Tags ViewController:
class ChecklistAddTagVC: UIViewController {
// Interface Links
#IBOutlet weak var tagsTitleLabel: UILabel!
#IBOutlet weak var tagsTableView: UITableView!
// Properties
var tagsDictionary: [Int: Tag] = [:]
var tagsAdded: [Int:Tag] = [:]
var tagsCallback: (([Int:Tag]) -> ())?
var indexForSelectedRow: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
tagsTableView.tableFooterView = UIView()
tagsDictionary = [
1: Tag(remoteID: 1, categoryID: 1, name: "Tag1", colour: "red"),
2: Tag(remoteID: 2, categoryID: 1, name: "Tag2", colour: "blue"),
3: Tag(remoteID: 3, categoryID: 1, name: "Tag3", colour: "orange"),
4: Tag(remoteID: 4, categoryID: 1, name: "Tag4", colour: "black")
]
print("Received index for SelectedRow: \(indexForSelectedRow ?? IndexPath())")
}
}
extension ChecklistAddTagVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tagsDictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "defectAndDamageTagCell", for: indexPath) as! ChecklistAddTagCell
cell.configCell()
cell.delegate = self
cell.tagNameLabel.text = tagsDictionary[indexPath.row + 1]?.name.capitalized
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
extension ChecklistAddTagVC: ChecklistAddTagCellDelegate{
// When the user press Add Tag then will be added in a dictionary and sent to ChecklistVC using a callback closure.
func addTagBtnPressed(button: UIButton, tagLabel: UILabel) {
if button.currentTitle == "+"{
button.setTitle("-", for: UIControl.State.normal)
tagLabel.textColor = UIColor.orange
tagsAdded = [0: Tag(remoteID: 1, categoryID: 1, name: tagLabel.text ?? String(), colour: "red")]
print(tagsAdded[0]?.name ?? String())
tagsCallback?(tagsAdded)
}
else{
button.setTitle("+", for: UIControl.State.normal)
tagLabel.textColor = UIColor.black
tagsAdded.removeValue(forKey: 0)
print(tagsAdded)
tagsCallback?(tagsAdded)
}
}
}
Here is a capture with my issue:
Thank you for reading this !
I fix it !
The solution is below. Also you can find the completed project at this link:
https://github.com/tygruletz/AppendCommentsToCells
MainVC:
class ChecklistVC: UIViewController {
#IBOutlet weak var questionsTableView: UITableView!
//Properties
lazy var itemSections: [ChecklistItemSection] = {
return ChecklistItemSection.checklistItemSections()
}()
var lastIndexPath: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
questionsTableView.reloadData()
}
}
extension ChecklistVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itemCategory = itemSections[section]
return itemCategory.checklistItems.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return itemSections.count
}
// Set the header of each section
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let checklistItemCategory = itemSections[section]
return checklistItemCategory.name.uppercased()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "checklistCell", for: indexPath) as! ChecklistCell
let itemCategory = itemSections[indexPath.section]
let item = itemCategory.checklistItems[indexPath.row]
cell.delegate = self
cell.configCell(item)
cell.vehicleCommentLabel.text = item.vehicleComment
cell.trailerCommentLabel.text = item.trailerComment
let sortedTagNames = item.vehicleTags.keys.sorted(by: {$0 < $1}).compactMap({ item.vehicleTags[$0]})
print("Sorted tag names: \(sortedTagNames.map {$0.name})")
let joinedTagNames = sortedTagNames.map { $0.name}.joined(separator: ", ")
cell.tagNameLabel.text = joinedTagNames
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goChecklistAddComment" {
let addCommentVC = segue.destination as! ChecklistAddCommentVC
addCommentVC.delegate = self
}
if segue.identifier == "goChecklistAddTag" {
let addTagVC = segue.destination as! ChecklistAddTagVC
addTagVC.delegate = self
addTagVC.addedTags = itemSections[lastIndexPath.section].checklistItems[lastIndexPath.row].vehicleTags
}
}
}
extension ChecklistVC: ChecklistCellDelegate {
func tapGestureOnCell(_ cell: ChecklistCell) {
showOptionsOnCellTapped(questionsTableView.indexPath(for: cell)!)
}
func showOptionsOnCellTapped(_ indexPath: IndexPath){
let addComment = UIAlertAction(title: "📝 Add Comment", style: .default) { action in
self.lastIndexPath = indexPath
self.performSegue(withIdentifier: "goChecklistAddComment", sender: nil)
}
let addTag = UIAlertAction(title: "🏷 Add Tag ⤵", style: .default) { action in
self.showOptionsForAddTag(indexPath)
}
let actionSheet = configureActionSheet()
actionSheet.addAction(addComment)
actionSheet.addAction(addTag)
self.present(actionSheet, animated: true, completion: nil)
}
// A menu from where the user can choose to add tags for Vehicle or Trailer
func showOptionsForAddTag(_ indexPath: IndexPath){
self.lastIndexPath = indexPath
let addVehicleTag = UIAlertAction(title: "Add Vehicle tag", style: .default) { action in
self.performSegue(withIdentifier: "goChecklistAddTag", sender: nil)
}
let addTrailerTag = UIAlertAction(title: "Add Trailer tag", style: .default) { action in
self.performSegue(withIdentifier: "goChecklistAddTag", sender: nil)
}
let actionSheet = configureActionSheet()
actionSheet.addAction(addVehicleTag)
actionSheet.addAction(addTrailerTag)
self.present(actionSheet, animated: true, completion: nil)
}
func configureActionSheet() -> UIAlertController {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
actionSheet.addAction(cancel)
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){
actionSheet.popoverPresentationController?.sourceView = self.view
actionSheet.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
actionSheet.popoverPresentationController?.permittedArrowDirections = []
}
return actionSheet
}
}
// Receive Comments from ChecklistAddCommentVC using the Delegate Pattern
extension ChecklistVC: ChecklistAddCommentDelegate {
func receiveVehicleComment(vehicleComment: String?, trailerComment: String?) {
let item = itemSections[lastIndexPath.section].checklistItems[lastIndexPath.row]
item.vehicleComment = vehicleComment ?? String()
item.trailerComment = trailerComment ?? String()
questionsTableView.reloadData()
}
}
// Receive Tags from ChecklistAddTagVC using the Delegate Pattern
extension ChecklistVC: ChecklistAddTagVCDelegate{
func receiveAddedTags(tags: [Int : Tag]) {
let item = self.itemSections[self.lastIndexPath.section].checklistItems[self.lastIndexPath.row]
item.vehicleTags = tags
}
}
AddTagsVC:
protocol ChecklistAddTagVCDelegate {
func receiveAddedTags(tags: [Int: Tag])
}
class ChecklistAddTagVC: UIViewController {
// Interface Links
#IBOutlet weak var tagsTableView: UITableView!
// Properties
var tagsDictionary: [Int: Tag] = [:]
var addedTags: [Int: Tag] = [:]
var delegate: ChecklistAddTagVCDelegate?
var indexPathForBtn: IndexPath!
override func viewDidLoad() {
super.viewDidLoad()
tagsTableView.tableFooterView = UIView()
tagsDictionary = [
1: Tag(remoteID: 1, categoryID: 1, name: "Tag1", color: "red"),
2: Tag(remoteID: 2, categoryID: 1, name: "Tag2", color: "blue"),
3: Tag(remoteID: 3, categoryID: 1, name: "Tag3", color: "orange"),
4: Tag(remoteID: 4, categoryID: 1, name: "Tag4", color: "black")
]
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Added tags: \(addedTags.map {$1.name})")
setupButtons()
tagsTableView.reloadData()
}
}
extension ChecklistAddTagVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tagsDictionary.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "defectAndDamageTagCell", for: indexPath) as! ChecklistAddTagCell
cell.configCell()
cell.delegate = self
cell.tagNameLabel.text = tagsDictionary[indexPath.row + 1]?.name.capitalized
indexPathForBtn = indexPath
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}
extension ChecklistAddTagVC: ChecklistAddTagCellDelegate{
// When the user press Add Tag then will be added in a dictionary and sent to ChecklistVC using a callback closure.
func addTagBtnPressed(button: UIButton, tagLabel: UILabel) {
let buttonPosition: CGPoint = button.convert(CGPoint.zero, to: tagsTableView)
let indexPath = tagsTableView.indexPathForRow(at: buttonPosition)
let indexPathForBtn: Int = indexPath?.row ?? 0
let tag: Tag = tagsDictionary[indexPathForBtn + 1] ?? Tag(remoteID: 0, categoryID: 0, name: String(), color: String())
if button.currentTitle == "+"{
button.setTitle("-", for: UIControl.State.normal)
tagLabel.textColor = UIColor.orange
// Add selected tag to Dictionary when the user press +
addedTags[tag.remoteID] = tag
}
else{
button.setTitle("+", for: UIControl.State.normal)
tagLabel.textColor = UIColor.black
// Delete selected tag from Dictionary when the user press -
addedTags.removeValue(forKey: tag.remoteID)
}
// Send the Dictionary to ChecklistVC
if delegate != nil{
delegate?.receiveAddedTags(tags: addedTags)
}
print("\n ****** UPDATED DICTIONARY ******")
print(addedTags.map {"key: \($1.remoteID) - name: \($1.name)"})
}
// Setup the state of the buttons and also the color of the buttons to be orange if that Tag exist in `addedTags` dictionary.
func setupButtons(){
for eachAddedTag in addedTags {
if eachAddedTag.value.remoteID == tagsDictionary[1]?.remoteID {
print(eachAddedTag)
}
}
}
}
And here is how looks now:
Can you try to handle tableview:didselectrowatindexpath instead of using the segue and on didselectrowatindexpath show the add tag vc.
I made a UITextfield to receive the data from user.
I want to convert a value from UITextField to UILabel.
I did it in simple UIView, which has only two object, UITextField and UILabel.
This is the code that works.
class ViewController: UIViewController, UITextFieldDelegate {
let inputNumber = UITextField(frame: CGRect(x: 150.0, y: 100.0, width: 200.0, height: 50.0))
let outputNumber = UILabel(frame: CGRect(x: 150.0, y: 200.0, width: 200.0, height: 50.0))
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override func viewDidLoad() {
super.viewDidLoad()
calculatePrice()
}
func calculatePrice () {
priceInputLabel.keyboardType = .numberPad
priceInputLabel.clearButtonMode = .whileEditing
self.view.addSubview(priceInputLabel)
toolBarKeyBoard.sizeToFit()
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
priceInputLabel.inputAccessoryView = toolBarKeyBoard
}
#objc func donePressed() {
view.endEditing(true)
result = inputNumber.text!
let convertedNumber = (result as NSString).doubleValue
if Int(inputNumber.text!) == nil {
outputNumber.text = String("Nil")
} else {
outputNumber.text = String(Int(convertedNumber * 0.85))
}
}
}
But in other case, down below, the problem is UITextField and UILabel are in the UITableViewCell as subviews.
I made a 3 swift files. 2 files are UITableViewCell subclasses, and 1 file is a UITableView class.
1. FruitTableViewCell : UITableViewCell subclass
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(donePressed))
var result : String!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitTextField.endEditing(true)
}
}
2. AnotherFruitTableViewCell : UITableViewCell subclass
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}
3. TableViewController : UITableViewController class
class TableViewController: UITableViewController, UITextFieldDelegate {
let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
}
The fruitsTextField and fruitsTextLabel is not in the same class like in the first example code.
So, I cannot call both instances and calculate a value in ViewController class. Of course, cannot return a calculated value.
And, I'm not sure I can return after touching a done button to get a value from UITextField to UILabel, because the cells which is the super view of subview(UITextField and UILabel) are reproduced. I'm confusing touching a done button occurs dequeueing cells again.
How can I return a value from UITextField to UILabel in UITableViewCell?
Thanks!
If I understand correctly you want to change some parameter of one cell based on action in another cell (actually the fact that these cells are of different classes is not important to that matter). ViewController will be in that case intermediary, so you need to make cells to communicate with ViewController. For communication between two objects one usually uses delegate or closure pattern.
In that case I would use closure. So, when you instantiate the cell with TextField ViewController tells Cell what to do when Done is pressed. To achieve that:
add var didEntered: ((_ text: String)->())? to FruitTableViewCell
add didEntered?(fruitsTextField.text ?? "") to #objc func donePressed()
add (the code updates the second row based on textfield value - just for a example)
cell.didEntered = {text in
self.fruitsComponents[1] = text
self.tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: UITableViewRowAnimation.none)}
to override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
On the way I corrected some mistakes to make it work, so the complete code is below.
Upd 1
import UIKit
class ViewController: UITableViewController, UITextFieldDelegate {
var fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
var fruitsLabels: [String] = ["", "", "", ""]
let cellReuseidentifier = "cell"
let anotherCellReuseidentifier = "anotherCell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.didEntered = {text in
self.fruitsLabels = Array(repeating: text, count: 4)
self.tableView.reloadData()
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
cell.fruitsTextLabel.text = fruitsLabels[indexPath.row]
return cell
}
}
}
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
let toolBarKeyBoard = UIToolbar()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
var result : String!
var didEntered: ((_ text: String)->())?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
fruitsTextField.backgroundColor = .yellow
fruitsTextField.textColor = UIColor(red: CGFloat(242/255.0), green: CGFloat(56/255.0), blue: CGFloat(90/255.0), alpha: 1.0)
fruitsTextField.keyboardType = .numberPad
fruitsTextField.clearButtonMode = .whileEditing
toolBarKeyBoard.sizeToFit()
fruitsTextField.inputAccessoryView = toolBarKeyBoard
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
}
#objc func donePressed() {
fruitsTextField.endEditing(true)
didEntered?(fruitsTextField.text ?? "")
}
}
class AnotherFruitTableViewCell: UITableViewCell {
var fruitsTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextLabel.backgroundColor = UIColor.brown
fruitsTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
}
}
You can use UITextField delegate methods to achieve this in your TableViewController.
In your TableViewController cellForRowAt method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row] //"I am confused why it is here"
cell.fruitsTextField.delegate = self
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
Now add UITextField Delegate methods:
extension TableViewController : UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
print(textField.text!)
let cell = self.tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as! AnotherFruitTableViewCell
cell.textLabel.text = textField.text!
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.endEditing(true)
return true
}
}
Hope this helps.
I have a tableview (SettingsViewController) which I use as a user info view where user info is displayed (name, email, phone #, etc). This is similar to the standard iOS contact page.
Each cell has a textfield which is stretched accross the size of the cell, so that once in "edit" mode the user can update his/her info.
I also have a custom Cell (SettingsCell) which is where i setup the cell with the textfield etc.
SettingsViewController (excluded a lot of tabelview setup code):
class SettingsViewController: UITableViewController{
let cellId = "cellId"
var apiController: APIController?
var firstName: String?
var lastName: String?
var email: String?
var phoneNumber: String?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .mainWhite()
tableView = UITableView(frame: CGRect.zero, style: .grouped)
tableView.register(SettingsCell.self, forCellReuseIdentifier: cellId)
setupNavigation()
}
fileprivate func setupNavigation() {
editButtonItem.action = #selector(showEditing)
editButtonItem.title = "Edit"
editButtonItem.tintColor = .mainWhite()
self.navigationItem.rightBarButtonItem = editButtonItem
}
#objc func showEditing(sender: UIBarButtonItem)
{
if(self.tableView.isEditing == false)
{
self.tableView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Save"
self.tableView.reloadData()
}
else
{
self.tableView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Edit"
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! SettingsCell
if self.tableView.isEditing == true {
cell.textField.isEnabled = true
if indexPath.section == 2 {
cell.textField.keyboardType = .phonePad
}
} else {
cell.textField.isEnabled = false
}
cell.selectionStyle = .none
filloutUserInfo(indexPath: indexPath, cell: cell)
return cell
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
//THIS NEVER GETTING EXECUTED
override func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
print("editing done for row \(indexPath?.item)")
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
}
Settings Cell:
class SettingsCell: UITableViewCell, UITextFieldDelegate {
let textField: UITextField = {
let tf = UITextField()
tf.isEnabled = false
return tf
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
addSubview(textField)
textField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: 0, height: 0)
textField.addDoneButtonOnKeyboard()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem I've now encountered is that after I have gone into edit mode, and changed the text of a given cell, the tableview doesn't actually recognize this. The didEndEditingRowAt never gets called and that print statement is never displayed. I am suspecting that is has something to do with the textfield not being connected to the tableviewcontroller in any way, but I'm not sure how to fix this.
I need to be able to know when a user has finished editing in order to display an alert for improper formatting and disabling the save button.
You need to implement a callback to listen the textField endEditing event from SettingsCell to your ViewController.
To achieve this, here is the updated SettingsCell
class SettingsCell: UITableViewCell, UITextFieldDelegate {
let textField: UITextField = {
let tf = UITextField()
tf.isEnabled = false
tf.addDoneButtonOnKeyboard()
return tf
}()
public var onEndEditing: ((String?) -> Void)?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
textField.removeFromSuperview()
addSubview(textField)
textField.delegate = self
textField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: 0, height: 0)
}
func textFieldDidEndEditing(_ textField: UITextField) {
self.onEndEditing?(textField.text)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now update cellForRowAt to listen the endEditing event as below,
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! SettingsCell
if self.tableView.isEditing == true {
cell.textField.isEnabled = true
cell.onEndEditing = { text in
print("Cell Editing finished with text: \(text)")
}
if indexPath.section == 2 {
cell.textField.keyboardType = .phonePad
}
} else {
cell.textField.isEnabled = false
}
cell.selectionStyle = .none
filloutUserInfo(indexPath: indexPath, cell: cell)
return cell
}
iOS 10 has a feature I would like to replicate. When you 3D touch an album in the Apple Music app it opens the menu shown below. However unlike a normal peek and pop, it does not go away when you raise you finger. How do I replicate this?
The closest I got to replicating it is the following code.. It create a dummy-replica of the Music application.. Then I added the PeekPop-3D-Touch delegates.
However, in the delegate, I add an observer to the gesture recognizer and then cancel the gesture upon peeking but then re-enable it when the finger is lifted. To re-enable it, I did it async because the preview will disappear immediately without the async dispatch. I couldn't find a way around it..
Now if you tap outside the blue box, it will disappear like normal =]
http://i.imgur.com/073M2Ku.jpg
http://i.imgur.com/XkwUBly.jpg
//
// ViewController.swift
// PeekPopExample
//
// Created by Brandon Anthony on 2016-07-16.
// Copyright © 2016 XIO. All rights reserved.
//
import UIKit
class MusicViewController: UITabBarController, UITabBarControllerDelegate {
var tableView: UITableView!
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.initControllers()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func initControllers() {
let libraryController = LibraryViewController()
let forYouController = UIViewController()
let browseController = UIViewController()
let radioController = UIViewController()
let searchController = UIViewController()
libraryController.title = "Library"
libraryController.tabBarItem.image = nil
forYouController.title = "For You"
forYouController.tabBarItem.image = nil
browseController.title = "Browse"
browseController.tabBarItem.image = nil
radioController.title = "Radio"
radioController.tabBarItem.image = nil
searchController.title = "Search"
searchController.tabBarItem.image = nil
self.viewControllers = [libraryController, forYouController, browseController, radioController, searchController];
}
}
And the implementation of ForceTouch pausing..
//
// LibraryViewController.swift
// PeekPopExample
//
// Created by Brandon Anthony on 2016-07-16.
// Copyright © 2016 XIO. All rights reserved.
//
import Foundation
import UIKit
//Views and Cells..
class AlbumView : UIView {
var albumCover: UIImageView!
var title: UILabel!
var artist: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initControls() {
self.albumCover = UIImageView()
self.title = UILabel()
self.artist = UILabel()
}
func setTheme() {
self.albumCover.contentMode = .scaleAspectFit
self.albumCover.layer.cornerRadius = 5.0
self.albumCover.backgroundColor = UIColor.lightGray()
self.title.text = "Unknown"
self.title.font = UIFont.systemFont(ofSize: 12)
self.artist.text = "Unknown"
self.artist.textColor = UIColor.lightGray()
self.artist.font = UIFont.systemFont(ofSize: 12)
}
func doLayout() {
self.addSubview(self.albumCover)
self.addSubview(self.title)
self.addSubview(self.artist)
let views = ["albumCover": self.albumCover, "title": self.title, "artist": self.artist];
var constraints = Array<String>()
constraints.append("H:|-0-[albumCover]-0-|")
constraints.append("H:|-0-[title]-0-|")
constraints.append("H:|-0-[artist]-0-|")
constraints.append("V:|-0-[albumCover]-[title]-[artist]-0-|")
let aspectRatioConstraint = NSLayoutConstraint(item: self.albumCover, attribute: .width, relatedBy: .equal, toItem: self.albumCover, attribute: .height, multiplier: 1.0, constant: 0.0)
self.addConstraint(aspectRatioConstraint)
for constraint in constraints {
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
class AlbumCell : UITableViewCell {
var firstAlbumView: AlbumView!
var secondAlbumView: AlbumView!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.initControls()
self.setTheme()
self.doLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func initControls() {
self.firstAlbumView = AlbumView(frame: CGRect.zero)
self.secondAlbumView = AlbumView(frame: CGRect.zero)
}
func setTheme() {
}
func doLayout() {
self.contentView.addSubview(self.firstAlbumView)
self.contentView.addSubview(self.secondAlbumView)
let views: [String: AnyObject] = ["firstAlbumView": self.firstAlbumView, "secondAlbumView": self.secondAlbumView];
var constraints = Array<String>()
constraints.append("H:|-15-[firstAlbumView(==secondAlbumView)]-15-[secondAlbumView(==firstAlbumView)]-15-|")
constraints.append("V:|-15-[firstAlbumView]-15-|")
constraints.append("V:|-15-[secondAlbumView]-15-|")
for constraint in constraints {
self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.contentView.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
}
//Details..
class DetailSongViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blue()
}
/*override func previewActionItems() -> [UIPreviewActionItem] {
let regularAction = UIPreviewAction(title: "Regular", style: .default) { (action: UIPreviewAction, vc: UIViewController) -> Void in
}
let destructiveAction = UIPreviewAction(title: "Destructive", style: .destructive) { (action: UIPreviewAction, vc: UIViewController) -> Void in
}
let actionGroup = UIPreviewActionGroup(title: "Group...", style: .default, actions: [regularAction, destructiveAction])
return [actionGroup]
}*/
}
//Implementation..
extension LibraryViewController : UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = self.tableView.indexPathForRow(at: location) else {
return nil
}
guard let cell = self.tableView.cellForRow(at: indexPath) else {
return nil
}
previewingContext.previewingGestureRecognizerForFailureRelationship.addObserver(self, forKeyPath: "state", options: .new, context: nil)
let detailViewController = DetailSongViewController()
detailViewController.preferredContentSize = CGSize(width: 0.0, height: 300.0)
previewingContext.sourceRect = cell.frame
return detailViewController
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
//self.show(viewControllerToCommit, sender: self)
}
override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer<Void>?) {
if let object = object {
if keyPath == "state" {
let newValue = change![NSKeyValueChangeKey.newKey]!.integerValue
let state = UIGestureRecognizerState(rawValue: newValue!)!
switch state {
case .began, .changed:
self.navigationItem.title = "Peeking"
(object as! UIGestureRecognizer).isEnabled = false
case .ended, .failed, .cancelled:
self.navigationItem.title = "Not committed"
object.removeObserver(self, forKeyPath: "state")
DispatchQueue.main.async(execute: {
(object as! UIGestureRecognizer).isEnabled = true
})
case .possible:
break
}
}
}
}
}
class LibraryViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.initControls()
self.setTheme()
self.registerClasses()
self.registerPeekPopPreviews();
self.doLayout()
}
func initControls() {
self.tableView = UITableView(frame: CGRect.zero, style: .grouped)
}
func setTheme() {
self.edgesForExtendedLayout = UIRectEdge()
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
func registerClasses() {
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Default")
self.tableView.register(AlbumCell.self, forCellReuseIdentifier: "AlbumCell")
}
func registerPeekPopPreviews() {
//if (self.traitCollection.forceTouchCapability == .available) {
self.registerForPreviewing(with: self, sourceView: self.tableView)
//}
}
func doLayout() {
self.view.addSubview(self.tableView)
let views: [String: AnyObject] = ["tableView": self.tableView];
var constraints = Array<String>()
constraints.append("H:|-0-[tableView]-0-|")
constraints.append("V:|-0-[tableView]-0-|")
for constraint in constraints {
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
}
for view in self.view.subviews {
view.translatesAutoresizingMaskIntoConstraints = false
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return section == 0 ? 5 : 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (indexPath as NSIndexPath).section == 0 ? 44.0 : 235.0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 75.0 : 50.0
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.0001
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return section == 0 ? "Library" : "Recently Added"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath as NSIndexPath).section == 0 { //Library
let cell = tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath)
switch (indexPath as NSIndexPath).row {
case 0:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Playlists"
case 1:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Artists"
case 2:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Albums"
case 3:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Songs"
case 4:
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = "Downloads"
default:
break
}
}
if (indexPath as NSIndexPath).section == 1 { //Recently Added
let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumCell", for: indexPath)
cell.selectionStyle = .none
return cell
}
return tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath)
}
}
This actually might be done using UIPreviewInteraction API.
https://developer.apple.com/documentation/uikit/uipreviewinteraction
It is almost similar to the Peek and Pop API.
Here we have 2 phases: Preview and Commit which are corresponding to the Peek and Pop in the later API. we have UIPreviewInteractionDelegate which gives us the access to the transition through these phases.
So what one should do is, to replicate the above Apple Music Popup,
Manually show a blur overlay during didUpdatePreviewTransition
Build an xib of the above menu and show it during didUpdateCommitTransition
You can make the view stay there on commitTransition phase end.
Actually, apple has built a demo of this in the form of a Chat App.
Download the sample code from here and test it out.
I wrote some code to replicate like apple music style peek and pop.
Work like below
Explanation
TopView.xib, TopView.swift (You can customize it)
PeekAndPopActionView.swift (View for single action, such as download, share ..)
PeekAndPopController.swift (Present, Dismiss the view)
ForceTouchGestureRecognizer.swift (Detect Force Touch)
Usage
fileprivate let peekedViewController = PeekAndPopController()
#IBAction func presentAction(_ sender: Any) {
present(peekedViewController, animated: true)
}
let forceTouch = ForceTouchGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
forceTouch.addTarget(self, action: #selector(touchAction(_:)))
forceTouch.cancelsTouchesInView = false
view.addGestureRecognizer(forceTouch)
let download = PeekAndPopActionView(text: "Download", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Download Action")
})
let playNext = PeekAndPopActionView(text: "Play Next", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Play Next Action")
})
let playLast = PeekAndPopActionView(text: "Play Later", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Play Last Action")
})
let share = PeekAndPopActionView(text: "Share", image: #imageLiteral(resourceName: "btnDownload"), handler: {
print("Share Action")
})
peekedViewController.addAction(download)
peekedViewController.addAction(playNext)
peekedViewController.addAction(playLast)
peekedViewController.addAction(share)
peekedViewController.topView = TopView().loadNib()
peekedViewController.topView?.handler = {
print("Play Play Play")
}
}
#objc func touchAction(_ gesture: ForceTouchGestureRecognizer) {
print(#function, gesture.touch?.location(in: view) ?? "")
present(peekedViewController, animated: true)
}
I am making a custom UIViewController that I want to show a message when a UITableViewCell is tapped. I create a UITableView and set the property
tableView.allowsSelectionDuringEditing = true
Even though that property has been set to true, tableView(:didSelectRowAtIndexPath) is not being called to display the message. Why is this and how do I fix it?
Here is my UITableViewController:
class GameListViewController: UIViewController, UITableViewDataSource, GameListViewDelegate, UITableViewDelegate
{
private var _games: [GameObject] = []
var tableView: UITableView {return (view as GameListView).tableView}
var gameListView: GameListView {return (view as GameListView) }
override func loadView()
{
view = GameListView(frame: UIScreen.mainScreen().bounds)
gameListView.delegate = self
}
override func viewDidLoad()
{
super.viewDidLoad()
tableView.dataSource = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return _games.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var index: Int = indexPath.row
let currentGame = _games[index]
var cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: nil)
cell.textLabel?.lineBreakMode = NSLineBreakMode.ByCharWrapping
cell.textLabel?.numberOfLines = 2
if currentGame.isFinished == true
{
cell.imageView?.image = UIImage(named: "Finished.png")
cell.textLabel?.text = "Winner: Player\(currentGame.playerMakingMove)\nMissiles Launched: \(currentGame.missileCount)"
}
else
{
cell.imageView?.image = UIImage(named: "Resume.png")
cell.textLabel?.text = "Turn: Player\(currentGame.playerMakingMove)\nMissiles Launched: \(currentGame.missileCount)"
}
return cell
}
/*Handles deleting the cells*/
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if (editingStyle == UITableViewCellEditingStyle.Delete)
{
var index: Int = indexPath.row
_games.removeAtIndex(index)
tableView.deleteRowsAtIndexPaths(NSArray(array: [indexPath]), withRowAnimation: UITableViewRowAnimation.Left)
}
tableView.reloadData()
}
/* Performs when a cell is touched */
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
var index: Int = indexPath.row
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
println("inside tableView: didSelectRowAtIndexPath")
var message = UIAlertView(title: "Row selected", message: "You have selected a row", delegate: nil, cancelButtonTitle: "Click ME!", otherButtonTitles: "no other buttons")
message.show()
}
func makeNewGame()
{
addGames(1)
tableView.reloadData()
}
func addGames(gameNumber: Int)
{
var p1 = Player()
var p2 = Player()
for var i = 0; i < gameNumber; i++
{
var randBool = Bool( round(drand48())) // True/False
var randPlayer:Int = Int( round( (drand48() + 1)) ) // 1 or 2
var randMissleCount:Int = Int( arc4random_uniform(5000) + 1 ) //1 - 5001
_games.append(GameObject(isFinished: randBool, playerMakingMove: randPlayer, missileCount: randMissleCount, player1: p1, player2: p2))
}
}
}
Here is my UIView that contains the UITableView:
protocol GameListViewDelegate: class
{
func makeNewGame()
}
class GameListView: UIView, PictureButtonDelegate
{
var newGameButton: PictureButton!
var tableView: UITableView!
weak var delegate: GameListViewDelegate? = nil
override init(frame: CGRect)
{
super.init(frame: frame)
newGameButton = PictureButton(frame: CGRect(), fileName: "NewGame.png")
newGameButton.backgroundColor = UIColor.grayColor()
newGameButton.delegate = self
newGameButton.setTranslatesAutoresizingMaskIntoConstraints(false)
tableView = UITableView()
tableView.allowsSelectionDuringEditing = true
tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.addSubview(newGameButton)
self.addSubview(tableView)
}
override func layoutSubviews()
{
let views: [String : UIView] = ["button": newGameButton, "tableView": tableView]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[button]-|", options: .allZeros, metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[tableView]-|", options: .allZeros, metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-(>=15,<=25)-[button]-[tableView]-|", options: .allZeros, metrics: nil, views: views))
}
func buttonPushed()
{
delegate?.makeNewGame()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You have to set a delegate for the UITableView and implement func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath).
You only set the dataSource, but never the delegate (only a GameListViewDelegate for GameListView).
Change your viewDidLoad function in GameListViewController to:
override func viewDidLoad()
{
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}