I am trying to call a UIAlertController from within my UITtableViewCell when my function is called. It gives me an error saying present is not available. I understand it's not within a ViewController. I am looking for an approach to access it.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let tapGestureShareImageView = UITapGestureRecognizer(target: self, action: #selector(self.shareImageTouchUpInside))
shareImageView.addGestureRecognizer(tapGestureShareImageView)
shareImageView.isUserInteractionEnabled = true
}
#objc func shareImageTouchUpInside() {
showAction()
}
func showAction() {
let alertController = UIAlertController(title: "Action Sheet", message: "What do you like to do", preferredStyle: .alert)
let okButton = UIAlertAction(title: "Done", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
let deleteButton = UIAlertAction(title: "Skip", style: .destructive, handler: { (action) -> Void in
print("Delete button tapped")
})
alertController.addAction(okButton)
alertController.addAction(deleteButton)
present(alertController, animated: true, completion: nil)
}
You can try to use delegate
protocol AlertShower{
func showAlert(TableCustomCell)
}
class TableCustomCell: UITableViewCell {
var delegate: AlertShower?
#IBAction func showClicked(_ sender: UIButton) {
self.delegate?.alertShower(sender:self)
}
}
in the VC
class viewController: UIViewController, AlertShower {
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = areaSettTable.dequeueReusableCell(withIdentifier:CellIdentifier1) as! TableCustomCell
cell.delegate = self
return cell
}
func showAlert(sender:TableCustomCell) {
// show alert here
}
}
Present is only available to ViewControllers. You are going to have to redirect the touch event to your view controller. The most common way of doing this would be having a delegate property in your UITableViewCell.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276
I ran into a similar problem myself when creating a custom activity indicator from a subclassed UIView. What I did was create the 'show' function (in the subclass) and pass in a UIViewController parameter, like so:
public func play(inView view: UIViewController) {
//Perform action here
view.present(alertController, animated: true, completion: nil)
}
Simply call it in your view controller like so:
CustomClass.play(inView: self)
Hopefully this helps!
Related
I’ve wrote that code using UIAlertController, what I should change to make it works with UIViewController?
Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
tableView.dataSource = self
Preformatted texttableView.delegate = self
}
}
Here is the part to show UIAlertController and I need change something bellow it to open ViewController.
extension SecondViewController: TableViewCellDelegate{
func tableViewCellDidTapItem(with viewModel: TileCollectionViewCellModel) {
let alert = UIAlertController(title: viewModel.name,
message: "",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Dismis",
style: .cancel,
handler: nil))
present(alert, animated: true)
}
}
And here is a part of my class for cell of objects. When I click on each of them, I need to see UIViewController instead of UIMessageAllert
protocol TableViewCellDelegate: AnyObject {
func tableViewCellDidTapItem(with viewModel: TileCollectionViewCellModel)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let viewModel = viewModels[indexPath.row]
delegatee?.tableViewCellDidTapItem(with: viewModel)
}
I have a tableview cell which contains a specific button for showing alert sheet.
I've learned that button itself can't be pressed inside of table view cell. It must be called from a view controller.
So I`ve added a callback closure like so:
class FeedViewCell: UITableViewCell {
var callback : (() -> ())?
static let reuseIdentifier: String = "FeedTableViewCell"
lazy var menuButton: UIButton = {
let btn = UIButton()
btn.isUserInteractionEnabled = true
btn.addTarget(self, action: #selector(menuTapped), for: .touchUpInside)
return btn
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(menuButton)
}
#objc func menuTapped() {
print("menu tapped")
callback?()
}
I suspect it might be a problem with table view cell registration.. Pls let me know if that's not it. And in the view controller I did this:
class FeedViewController: UIViewController {
// some code...
tableView.register(FeedViewCell.self, forCellReuseIdentifier: FeedViewCell.reuseIdentifier)
}
extension FeedViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: FeedViewCell.reuseIdentifier, for: indexPath) as! FeedViewCell
cell.callback = {
print("menu")
let actionSheet = UIAlertController(title: "", message: "", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: { action in
print("tap dismiss")
}))
actionSheet.addAction(UIAlertAction(title: "Follow", style: .default, handler: { action in
print("tap follow")
}))
self.present(actionSheet, animated: true, completion: nil)
}
return cell
}
}
So the main question is, why is the button not working? It doesn't even print "menu"
Thank you for all of your answers
This line is wrong.
addSubview(menuButton)
Instead:
contentView.addSubview(menuButton)
Try to add your button into cell's content view like this:
self.contentView.addSubview(self.menuButton)
This question already has an answer here:
Issue with UITableView: Action only works every second time
(1 answer)
Closed 3 years ago.
I already the TableView for displaying JSON Values from API. But the result of the click does not match the existing title, but it is worth the title from the previous click. More clearly see the picture
Code InfoViewCell.swift
This code for cell in tableview
import UIKit
class InfoViewCell: UITableViewCell {
#IBOutlet weak var imgInfo: UIImageView!
#IBOutlet weak var lblInfo: UILabel!
#IBOutlet weak var lblBerita: UILabel!
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
}
}
Code Info.swift this code for models
class Info {
var id_informasi: Int?
var tgl_informasi: String?
var judul: String?
var berita: String?
var foto: String?
init(id_informasi:Int?,judul: String?,berita: String?,foto: String?) {
self.id_informasi = id_informasi
self.judul = judul
self.berita = berita
self.foto = foto
}
}
Code InfoViewController.swift
import UIKit
import Alamofire
import AlamofireImage
class InformasiViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableInfo: UITableView!
var activityIndicator:UIActivityIndicatorView = UIActivityIndicatorView()
var infoes = [Info]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return infoes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellInfo", for: indexPath) as! InfoViewCell
//getting the hero for the specified position
let inpo: Info
inpo = infoes[indexPath.row]
//displaying values
cell.lblInfo.text = inpo.judul
cell.lblBerita.text = inpo.berita
//displaying image
Alamofire.request(inpo.foto!).responseImage { response in
debugPrint(response)
if let image = response.result.value {
cell.imgInfo.image = image
}
}
return cell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let defaultValues = UserDefaults.standard
let token = defaultValues.string(forKey: "token")
//the Web API URL
let URL_GET_DATA = "https://api.landslidepad.com/api/admin_desa/informasi_penting?token=" + token!
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
//fetching data from web api
Alamofire.request(URL_GET_DATA, method: .get).responseJSON
{
response in
//printing response
print(response)
self.activityIndicator.stopAnimating()
//getting the json value from the server
if let result = response.result.value {
let jsonData = result as! NSDictionary
//if there is no error
if((jsonData.value(forKey: "message") as! String == "Sukses!")){
//getting the user from response
let user = jsonData.value(forKey: "values") as! NSArray
for i in 0..<user.count{
//adding hero values to the hero list
self.infoes.append(Info(
id_informasi: (user[i] as AnyObject).value(forKey: "id_informasi") as? Int,
judul: (user[i] as AnyObject).value(forKey: "judul") as? String,
berita: (user[i] as AnyObject).value(forKey: "berita") as? String,
foto: (user[i] as AnyObject).value(forKey: "foto") as? String
))
}
//displaying data in tableview
self.tableInfo.reloadData()
}else{
let alert = UIAlertController(title: "Ada yang salah?", message: "Silahkan Ulangi Kembali!.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
}
self.tableInfo.reloadData()
// Do any additional setup after loading the view, typically from a nib.
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I already tried to create func tableview didDeselectRowAt indexPath,
but the value I want to display is not in line with my expectations. I will pass this value to the detailed view
Thanks
When you click on a row, that row is selected — and the previous selected row is deselected. Well, you have implemented didDeselect, so the previous selected row is displayed. Instead, implement didSelect.
instead of this
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
please use this
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let info: Info
info = infoes[indexPath.row]
//building an alert
let alertController = UIAlertController(title: info.judul, message: "", preferredStyle: .alert)
//the confirm action taking the inputs
let confirmAction = UIAlertAction(title: "Enter", style: .default) { (_) in
}
//the cancel action doing nothing
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (_) in }
//adding action
alertController.addAction(confirmAction)
alertController.addAction(cancelAction)
//presenting dialog
present(alertController, animated: true, completion: nil)
}
I have a view and delete button in each row of the table viewenter image description here. I would like to click each to delete or view the item inside. I've added two button functions, but how to know when I click delete or view details row number 2 then it will remove or view the row 2?
#IBAction func deleteBtn(_ sender: Any) {
let refreshAlert = UIAlertController(title: "Message", message: "Are you sure you want to remove this item from the cart?", preferredStyle: UIAlertControllerStyle.alert)
refreshAlert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { (action: UIAlertAction!) in
}))
refreshAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in
self.removeCartAPI()
let buttonTag = (sender as AnyObject).tag
//self.navigationController?.popViewController(animated: true)
}))
present(refreshAlert, animated: true, completion: nil)
}
#IBAction func viewDetailsBtn(_ sender: Any) {
let vc: ParcelSendParcelSummaryViewController? = self.storyboard?.instantiateViewController(withIdentifier: "pSummaryVC") as?ParcelSendParcelSummaryViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
I recommend the delegate pattern here, something like this:
protocol YourCellNameDelegate {
func didTapDelete(at cell: YourCellName)
func didTapView(at cell: YourCellName)
}
class YourCellName: UITableViewCell {
weak var delegate: YouCellNameDelegate?
...
#IBAction func didTouchDeleteButton(sender: Any) {
delegate?.didTapDelete(at: self)
}
// same for did tap view
}
class ParcelSendParcelSummaryViewController {
....
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(
withIdentifier: "Your identifier",
for: indexPath
) as? YourCellName {
cell.delegate = self
}
}
}
extension ParcelSendParcelSummaryViewController: YouCellNameDelegate {
func didTapDelete(at cell: YourCellName){
if let index = tableView.indexPathForCellView(cell: cell) {
tableView.beginUpdates()
tableView.deleteRows(at: index, with: .automatic)
// Make sure you delete the item from the data source here, between begin and end updates
tableView.endUpdates()
}
}
func didTapView(at cell: YourCellName) {
// same as above, with your view logic
}
}
I have a groceryList app
when you add an item to the category list it adds to the entire list of categories when is should not!
https://github.com/mrbryankmiller/Grocery-TableView-.git
class GroceryItemsTableViewController: UITableViewController {
//var groceryItem = ["Item1", "Item2", "Item3"]
//var groceryList = ["Breakfast","Lunch", "Dinner"]
#IBOutlet var groceryItemTableView: UITableView!
#IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {
///new way///
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
let textField = alertController.textFields![0]
groceryItem.items.append(textField.text!)
self.tableView.reloadData()
}
alertController.addAction(saveAction)
//Add text field
// alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
// textField.textColor = UIColor.blackColor()
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
//self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
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 groceryItem.items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)
cell.textLabel!.text = groceryItem.items [indexPath.row]
return cell
}
}
If you see carefully the declaration of your class groceryItem you have a static array of elements for every item in your grocery list so every time you add a new element it's shared among all the grocery items.
Instead you should have for each grocery item a list associated with each of its items.
You could define a new struct to save for each grocery item its list of item associated like in the following way:
struct GroceryItem {
var name: String
var items: [String]
}
The we are going to change a little the code in your GroceryListTableViewController to refactor the code according your new model, so it should be like the following:
GroceryListTableViewController:
class GroceryListTableViewController: UITableViewController, GroceryItemsTableViewControllerProtocol {
var groceryList = [GroceryItem]()
#IBAction func addButton(sender: UIBarButtonItem) {
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Category", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
let textField = alertController.textFields![0]
self.groceryList.append(GroceryItem(name: textField.text!, items: [String]()))
self.tableView.reloadData()
}
alertController.addAction(saveAction)
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
//edit button
self.navigationItem.leftBarButtonItem = self.editButtonItem()
groceryList.append(GroceryItem(name: "Breakfast", items: ["Item1", "Item2", "Item3"]))
groceryList.append(GroceryItem(name: "Lunch", items: ["Item1", "Item2", "Item3"]))
groceryList.append(GroceryItem(name: "Dinner", items: ["Item1", "Item2", "Item3"]))
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groceryList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("prototype1", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = groceryList [indexPath.row].name
return cell
}
// pass a tableview cell value to navigationBar title in swift//
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! GroceryItemsTableViewController
let cell = sender as! UITableViewCell
let idx = self.tableView.indexPathForSelectedRow?.row
destinationVC.delegate = self
destinationVC.itemList = groceryList[idx!].items
destinationVC.navigationItem.title = cell.textLabel?.text
}
func didAddGroceryItem(itemName: String) {
let idx = self.tableView.indexPathForSelectedRow?.row
groceryList[idx!].items.append(itemName)
}
func didRemoveGroceryItem(index: Int) {
let idx = self.tableView.indexPathForSelectedRow?.row
groceryList[idx!].items.removeAtIndex(index)
}
}
In the above I have refactored all the code regarding the new model, I put only the places where the code change the rest keep the same.
The thing you need to pass the item associated with the cell selected to the another UIViewController and you can do it very easily in your prepareForSegue. For that we need to get the index for the selected cell and pass the elements to the another UIViewController where we have a new array of [String] created as data source to show the items.
The another important point in the code is that the GroceryListTableViewController now implements a new protocol called GroceryItemsTableViewControllerProtocol. This protocol it's the way to notify to GroceryListTableViewController from the GroceryItemsTableViewController every time a new item is added to the list it's called the delegate pattern.
GroceryItemsTableViewController:
protocol GroceryItemsTableViewControllerProtocol: class {
func didAddGroceryItem(itemName: String)
func didRemoveGroceryItem(index: Int)
}
class GroceryItemsTableViewController: UITableViewController {
weak var delegate: GroceryItemsTableViewControllerProtocol?
var itemList: [String]!
#IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {
///new way///
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { [weak self] action -> Void in
guard let s = self else { return }
let textField = alertController.textFields![0]
s.itemList.append(textField.text!)
s.delegate?.didAddGroceryItem(textField.text!)
s.tableView.reloadData()
}
alertController.addAction(saveAction)
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)
cell.textLabel!.text = itemList[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
itemList.removeAtIndex(indexPath.row)
delegate?.didRemoveGroceryItem(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
}
EDIT:
To handle properly the deletion you should create a new delegate method no notify the GroceryListTableViewController that a item has been deleted and then delete it properly and you can see in the updated code above.
I hope this help you.