is it possible to call something like did select row
Button1(messageContact) or button2(sendFriendRequest)
And then have it do two seperate functions depending on which button you clicked within side the same cell for row ?
Ben
You can make a delegate for your cell's actions:
protocol UserCellDelegate: class {
func didTapSendMessage(_ sender: UserCell)
func didTapAddSendFriendRequest(_ sender: UserCell)
}
and add this line at your UserCell:
var delegate: UserCellDelegate?
So when ever user tap on sendFriendRequest or messageContact, you do this:
#IBAction func sendMessageTapped(_ sender: UIButton) {
delegate?.didTapSendMessage(self)
}
#IBAction func sendMessageTapped(_ sender: UIButton) {
delegate?.didTapSendRequest(self)
}
And than confirm to that delegate from your view controller:
class UserViewController: UserCellDelegate {
func didTapSendMessage(_ sender: UserCell) {
guard let tappedIndex = tableView.indexPath(for: sender),
let user = users[tappedIndex.section].content[tappedIndex.row] as? User else {
return
}
//Do what you need in order to send message to user
}
func didTapAddSendFriendRequest(_ sender: UserCell) {
guard let tappedIndex = tableView.indexPath(for: sender),
let user = users[tappedIndex.section].content[tappedIndex.row] as? User else {
return
}
//Do what you need in order to send friend request
}
}
Don't forget to set yourself as the cell's delegate:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.delegate = self
}
Related
Sorry, I'm a noob,
I am a bit stuck. I have been researching this for awhile and cannot find anything to help.
So, my problems is:
I have a Table View controller with a bunch of Cells (Depending on users contact address book). These Cells contain the users contacts information (Name and #) users can select up to 3 cells (Contacts).
That all works fine, I just need to know how to get the name and # labels data from each cell so I can display that information in another View Controller (CAContactsList) when I press the "Done" button (which I'm also stumped with).
My Current Table View Controller Class:
class AddContactsListTableView: UITableViewController {
var contacts = [FetchedContact]()
override func viewDidLoad() {
super.viewDidLoad()
fetchContacts()
}
private func fetchContacts() {
print("Attempting to fetch contacts")
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, error) in
if let error = error {
print("failed to request access", error)
return
}
if granted {
print("access granted")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
try store.enumerateContacts(with: request, usingBlock: { (contact, stopPointer) in
print(contact.givenName)
self.contacts.append(FetchedContact(firstName: contact.givenName, lastName: contact.familyName, telephone: contact.phoneNumbers.first?.value.stringValue ?? ""))
})
} catch let error {
print("Failed to enumerate contact", error)
}
} else {
print("access denied")
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return the number of rows
return contacts.count
}
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
//Max Number of contacts allowed to be selected
let limit = 3
if let selectedRows = tableView.indexPathsForSelectedRows {
if selectedRows.count == limit {
let alertController = UIAlertController(title: "Oops", message: "Sorry, but you are limited to only \(limit) Contacts", preferredStyle: UIAlertController.Style.alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: {action in}))
self.present(alertController, animated: true, completion: nil)
return nil
}
}
return indexPath
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "AddContactsCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? AddContactsCell
// Configure the cell...
cell?.NamesLabel.text = contacts[indexPath.row].firstName + " " + contacts[indexPath.row].lastName
cell?.NumberLabel.text = contacts[indexPath.row].telephone
return cell!
}
}
My Current Cell Class:
class AddContactsCell: UITableViewCell {
//Mark Properties
#IBOutlet weak var NamesLabel: UILabel!
#IBOutlet weak var NumberLabel: UILabel!
#IBOutlet weak var ButtonSelector: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// update UI with Checkmark when Selected
accessoryType = selected ? .checkmark : .none
}
}
And my Fetched Contacts Class
struct FetchedContact {
var firstName: String
var lastName: String
var telephone: String
}
Any help would be Greatly Appreciated!
Override the prepare(for segue: UIStoryboardSegue, sender: Any?) in the AddContactsListTableView class where you can pass the selected contacts to the next view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Pass the selected object to the new view controller.
if let selectedRows = tableView.indexPathsForSelectedRows {
let selectedContacts = selectedRows.map{contacts[$0.row]}
let newVC = segue.destination as! NewViewController
newVC.contacts = selectedContacts
}
}
See this tutorial for more.
So basically you are already on the right track querying the table view's indexPathsForSelectedRows since it will contain the indices you need to filter your contacts for. The selected contacts should be:
guard let selectedIndices = tableView.indexPathsForSelectedRows else { return }
let selectedContacts = selectedIndices.map { contacts[$0.item] }
[Edit]: Use a more concise version
I have textfields that should get a value displayed once returning from a TableViewController where user selects a cell. I get that value in unwindfunction, but textfieldsdon't get updated. When printing the value it prints correctly on unwinding, so unwindshould be set correctly, but it just don't get displayed in it's textfield. I also tried prepare(for unwind:in TableViewControllerbut with same results. Can you see what I'm doing wrong?
As always many thanks.
unwind function:
#IBAction func unwindToDetailsVc(segue: UIStoryboardSegue) {
//Insert function to be run upon dismiss of VC2
print("unwindSegue triggered")
if let vc = segue.source as? CityTableViewController {
print("segue source is city vc : \(String(describing: vc.city!))")
self.cityTextField.text = vc.city
}
if let vc = segue.source as? RegionTableViewController {
print("segue source is region vc : \(String(describing: vc.region!))")
self.regionTextField.text = vc.region
}
if let vc = segue.source as? CountryTableViewController {
print("segue source is country vc : \(String(describing: vc.country!))")
self.countryTextField.text = vc.country
}
}
didSelect in TableView:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CityTableViewCell
self.city = cell.cityLabel.text ?? ""
performSegue(withIdentifier: "unwindSegue", sender: self)
// self.dismiss(animated: true, completion: nil)
}
prepare for unwind:
override func prepare(for unwind: UIStoryboardSegue, sender: Any?) {
if unwind.identifier == "unwindSegue" {
if let detailsVc = unwind.destination as? ShopDetailsTableViewController {
detailsVc.cityTextField.text! = city
}
}
}
textfield delegate function:
func textFieldDidBeginEditing(_ textField: UITextField) {
print("Editing textfield")
if textField.accessibilityIdentifier == "city" {
print("Editing city textfield")
performSegue(withIdentifier: "citySegue", sender: self)
} else if textField.accessibilityIdentifier == "region" {
print("Editing regio textfield")
performSegue(withIdentifier: "regionSegue", sender: self)
} else if textField.accessibilityIdentifier == "country" {
print("Editing country textfield")
performSegue(withIdentifier: "countrySegue", sender: self)
}
}
You can simply use a closure to solve this kind of problem statement,
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func openTableVC(_ sender: UIButton) {
if let controller = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TableViewController") as? TableViewController {
controller.handler = {[weak self](city) in
DispatchQueue.main.async {
self?.textField.text = city
}
}
self.navigationController?.pushViewController(controller, animated: true)
}
}
}
class TableViewController: UITableViewController {
var handler: ((String)->())?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let city = "Delhi"
self.handler?(city)
self.navigationController?.popViewController(animated: true)
}
}
The above code is generic and will work in every case from where you want to open TableViewController.
I finally found out what I was doing wrong, I was calling the functions loading from CoreDatauser details and displaying them in viewWillAppear. Once I moved them in saveButtonafter the saving to CoreDatafunction call, it all works as expected. Textfield get updated with select values from tableviews.
Many thank to #PGDev for sharing a more convenient way of coding this, without all the if elsestatements and unwinds. Great example of higher level coding.
I am trying to save the data I got from the JSON fire however, because of Alamofire's async nature I dont get the data I need instantly but only when I tap on the tableviewcell again (and the data is wrong too)
I am wondering what I should do here so that when I tap the tableviewcell it will get the data I needed (instead of empty arrays)
Here's my code:
class CurrencyExchangeViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
let exchange = currencyExchangeModel()
var past30DaysDateValueToPass = [String]()
var past30DaysPriceValueToPass = [Double]()
var valueToPass = ""
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath) as? CurrencyExchangeTableViewCell
valueToPass = Array(self.exchange.currencyToGetExchangesDictionary.keys)[indexPath.row]
self.getPastData(currency: valueToPass)
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "currencyHistorySegue") {
var viewController = segue.destination as? CurrencyHistoricalDataViewController
viewController?.historicalCurrency = valueToPass
viewController?.past30DaysPrice = self.exchange.currencyPast30DaysPriceArray
viewController?.past30DaysDate = self.exchange.currencyPast30DaysDatesArray
}
}
func getPastData(currency: String){
Alamofire.request("https://api.coindesk.com/v1/bpi/historical/close.json?currency=\(currency)").responseJSON{ (responseData) in
if responseData.result.value != nil {
let responseJSON = JSON(responseData.result.value)["bpi"].dictionaryObject
self.exchange.currencyPast30DaysDatesArray = Array(responseJSON!.keys)
self.exchange.currencyPast30DaysPriceArray = Array(responseJSON!.values) as! [Double]
}
}
}
}
If you want performSegue execute after getting data from Alamofire then remove
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
this line to from didselect and place at getPastData.
func getPastData(currency: String){
Alamofire.request("https://api.coindesk.com/v1/bpi/historical/close.json?currency=\(currency)").responseJSON{ (responseData) in
if responseData.result.value != nil {
let responseJSON = JSON(responseData.result.value)["bpi"].dictionaryObject
self.exchange.currencyPast30DaysDatesArray = Array(responseJSON!.keys)
self.exchange.currencyPast30DaysPriceArray = Array(responseJSON!.values) as! [Double]
performSegue(withIdentifier: "currencyHistorySegue", sender: self)
}
}
This will help.
I'm having two UICollectionView
#IBOutlet weak var collectionView1: UICollectionView!
#IBOutlet weak var collectionview2: UICollectionView!
i'm getting indexPath for each collection view separately with functions.
func getIndexPathForSelectedCell() -> NSIndexPath?
{
var indexPath:NSIndexPath?
if collectionview1.indexPathsForSelectedItems()!.count > 0 {
indexPath = collectionview1.indexPathsForSelectedItems()![0]
}
return indexPath
}
func getIndexPathForSelectedCell2() -> NSIndexPath?
{
var indexPath2:NSIndexPath?
if collectionView2.indexPathsForSelectedItems()!.count > 0 {
indexPath2 = collectionView2.indexPathsForSelectedItems()![0]
}
return indexPath2
}
I'm Performing segue for cell touch as follows.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if let indexPath = getIndexPathForSelectedCell()
{
let DealsdetailViewController = segue.destinationViewController as! DealsDetailViewController
DealsdetailViewController.Dealsdata = Dealsdata[indexPath.row]
}
else if let indexPath2 = getIndexPathForSelectedCell2()
{
let ContainerviewController = segue.destinationViewController as! ContainerViewController
ContainerviewController.BTdata = BTdata[indexPath2.row]
}
}
if i click on a cell in first collection view segue performs correctly, when i click on a cell in second collection view
i got error
in
let DealsdetailViewController = segue.destinationViewController as! DealsDetailViewController
which is first if statement condition value, i'm stuck here
please help me, how to handle performing both segue on cell click on each collection view.
Use the method from UICollectionView protocol
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = cellForItemAtIndexPath(indexPath)!
if collectionView == self.collectionView1 {
self.performSegueWithIdentifier("segue1", sender: cell)
} else if collectionView == self.collectionView2 {
self.performSegueWithIdentifier("segue2", sender: cell)
}
}
func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if segue.identifer == "segue1" {
let detailVC:DetailViewController = segue.destinationViewController as DetailViewController
// Your sender is cell. You have indexPath of them and can get his identity in dataSource.
//detailVC.name = ...
//detailVC.surname = ...
} else if segue.identifier == "segue2" {
//...
}
}
I'm building an app that asks users to select a location if they don't allow access to their current location using a Modal that Presents Modally as soon as the user clicks 'Deny'. This modal has information displayed as a TableView, and the modal dismisses as soon as the user selects a row. I save this selection in a variable called selectedStop. I want the app to pause until the user selects a location, then as soon as the user selects a location, the app continues and the setUpMap() function executes. I've tried using an infinite while loop in setUpMap() and using a boolean to break out of it as soon as a user selects a row, but the while loop executes before the Modal even pops up.
ViewController.swift
class ViewController: UIViewController {
var selectedStop: Int!
override func viewDidLoad() {
super.viewDidLoad()
// If we don't have access to the user's current location, request for it
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.AuthorizedWhenInUse) {
locationManager.requestWhenInUseAuthorization()
}
}
func setUpMap() {
// do stuff with var selectedStop
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .Denied:
// if user denies access, display modal
self.performSegueWithIdentifier("NotifyModally", sender: self)
setUpMap() // need this func to execute AFTER location is selected
break
case .AuthorizedWhenInUse:
setUpMap()
break
default:
break
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "NotifyModally") {
let destViewController:ModalViewController = segue.destinationViewController as! ModalViewController
// send selectedStop var to ModalViewController
destViewController.selectedStop = selectedStop
}
}
}
ModalViewController.swift
class ModalViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var busStops = ["Stop 1", "Stop 2", "Stop 3"]
var selectedStop: Int!
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return busStops.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel!.text = busStops[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedStop = indexPath.row
dismissViewControllerAnimated(true, completion: nil)
}
}
Using a Int variable to pass information will not working since it's a value type which will get copied every time you pass it around. So that means when you change the selectedStop in the didSelectRowAtIndexPath method, the original selectedStop inside ViewController will still be nil or whatever it was.
And then, to answer your question. There are several ways to solve this.
You can either pass a block (instead an int) to the ModalViewController like this:
var stopSelectedHandler: (Int) -> Void = { selectedStop in
// Do something here.
// setUpMap()
}
You'll call this block inside the completion handler of dismissViewControllerAnimated.
You can use notification.
// Do this inside `ViewController`.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "setupMap:", name: "UserDidSelectStop", object: nil)
// And then post the notification inside `didSelectRowAtIndexPath`
NSNotificationCenter.defaultCenter().postNotificationName("UserDidSelectStop", object: nil, userInfo: ["selectedStop": 2])
// Change your setupMap to this
func setupMap(notification: NSNotification) {
guard let selectedStop = notification.userInfo?["selectedStop"] as? Int else { return }
// Now you can use selectedStop.
}
You can also use KVO, delegate, etc. Use whatever suits you.
Put the block like this:
class ViewController: UIViewController {
var stopSelectedHandler: (Int) -> Void = { selectedStop in
// Do something here.
// setUpMap()
}
....
}