Usability of a button inside a UICollectionViewCell? - swift

I have a ProductVC.swift (ProductViewController) file and a ProductCell.swift. The ProductVC contains a UICollectinView and ProductCell is a specific UICollectionViewCell.
ProductCell.xib looks like this:
ProductVC contains an array with all the cell data (products) and populates the cells.
My goal: The user should have the possibility to like an product. He can do it by clicking the like button on the top right corner of every cell. Every cell shows a specific product which is specified by a productID.
My Problem: The like button action (IBAction func) is in the ProductCell. ProductCell doesn´t have the cell data. Cell data is stored in ProductVC in an array. So I don´t know how catch the product(productID) the user wants to like.
My Tries: With the code below I can get the indexPath of the cell where the user clicked the like button. But I can´t use this indexPath to get the product data because the data is stored in ProductVC. I could also store the data in ProductCell but it is not a clean way. Is it possible mb to give this indexPath to the ProductVC?
extension UICollectionView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let center = view.center
let viewCenter = self.convert(center, from: view.superview)
let indexPath = self.indexPathForItem(at: viewCenter)
return indexPath
}
}
let superview = self.superview as! UICollectionView
if let indexPath = superview.indexPathForView(button) {
print(indexPath) // indexPath of the cell where the button was pressed
}
SOLVED Solution is a callback closure:
//UICollectionViewCell
var saveProductLike: ((_ index: Int) -> Void)?
#IBAction func likedButtonClicked(_ sender: UIButton) {
print("Liked button clicked!")
let productArrayIndex = calculateProductArrayIndex(for: sender)
saveProductLike?(productArrayIndex!)
}
//UIViewController
cell.saveProductLike = { (index) -> Void in
print(index)
}

There are several approaches to solve this but I'll talk about the most common one which is using delegation.
protocol ProductCellDelegate: AnyObject {
func productCellDidPressLikeButton(_ cell: ProductCell)
}
in ProductCell define a property weak var delegate: ProductCellDelegate? and in the target action of the like button inform your delegate
#objc private likeButtonPressed(_ sender: Any) {
delegate?.productCellDidPressLikeButton(self)
}
In your view controller you could conform to the protocol and implement it like this:
func productCellDidPressLikeButton(_ cell: ProductCell) {
guard let ip = collectionView.indexPath(for: cell) else { return }
// process event, get product via index...
}
Then you need to set the view controller to be the delegate in collectionView(_:willDisplay:forItemAt:) or
collectionView(_:cellForItemAt:): cell.delegate = self.

Related

Function creating 2 identical instances of class instead of only 1

I am relatively new to iOS development, any help will be greatly appreciated!
I'm trying to create a new instance of a class 'Event'.
class Event {
var EventName: String
var EventPhoto: UIImage?
init?(EventName: String, EventPhoto: UIImage?) {
guard !EventName.isEmpty else {
return nil
}
// Initial initilization of the values
self.EventName = EventName
self.EventPhoto = EventPhoto
// If some of the values are left blank, this will return nil to signal the problem
}
}
Below is the override function, which from my understanding is responsible for creating the instance:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let button = sender as? UIBarButtonItem, button === saveButton else {
os_log("Cancelling Action, The Save Button Was not Pressed", log: OSLog.default,type: .debug)
return
}
let EventName = NewEventNameField.text ?? ""
let EventPhoto = NewEventImage.image
event = Event(EventName: EventName, EventPhoto: EventPhoto)
}
From my understanding, the override function should create a new instance of the class, which would then be displayed in a table view controller displaying a table of 'events'. My problem here is; when the function is called by the "create instance" button, it creates 2 identical instances with the same EventName and EventPhoto extracted from a textfield and an image in the view controller. In the tableview, there are basically 2 events that are exactly the same being displayed, which is what I am having trouble with since I don't see the code calling init twice anywhere, and can't figure out why the instances was created twice. After being created the 2 instances act independently and function like 2 separate instances would.
Thanks!
Thank You for the help, I found the issue in TableViewController's code file:
#IBAction func unwindToEventList(sender: UIStoryboardSegue){
if let sourceViewController = sender.source as?
NewEventViewController, let event = sourceViewController.event{
if let selectedIndexPath = tableView.indexPathForSelectedRow {
events[selectedIndexPath.row] = event
tableView.reloadRows(at: [selectedIndexPath], with: .none)
} else {
//Adding a new event instead of editing it.
let newIndexPath = IndexPath(row: events.count, section: 0)
events.append(event)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
//let newIndexPath = IndexPath(row: events.count, section: 0)
//events.append(event)
//tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
Turns out that I accidentally appended the instance to the list events and inserted it into the table an extra time outside the if statement.

how to properly pass data from one viewcontroller's selected cell to previous viewcontroller?

suppose I have two viewcontroller called A and B. In my viewcontroller B I have a tableview in it. When I selected a cell in my tableview, I want to pass that information back to A.
I have a dictionary that is of the following:
myData = [String: DataModel]
where DataModel takes the form of
struct DataModel{
var address = ""
var name = ""
}
I want to send the selected cell's key in B back to A. How should I go about doing that?
thanks for your help
Add this before class BViewController:
protocol ClassBViewControllerDelegate: class {
func didSelectTableViewCell(onRow row: Int)
}
Create a delegate property in BViewController:
weak var delegate: ClassBViewControllerDelegate?
Implement tableView delegate method tableView(_:didSelectRowAt:)
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath) {
let row = indexPath.row
delegate?.didSelectTableViewCell(onRow: row)
}
Tell ClassAViewController that its conforms to ClassBViewControllerDelegate as such:
class ClassAViewController: UIViewController, ClassBViewControllerDelegate {
Bind ClassAViewController and ClassBViewController at an appropriate place in ClassAViewController such a, for instance, prepareForSegue:sender:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SegueIdentifierXYZ" {
if let vc = segue.destination as? ClassBViewController {
vc.delegate = self
}
}
}
Use delegate method didSelectTableViewCell(onRow row: Int) of delegate contract ClassBViewControllerDelegate in ClassAViewController:
func didSelectTableViewCell(onRow row: Int) {
print("Selected table view row is:", row)
}
Using completion block or by using delegates you can achieve this.
Using blocks you can code like this:
In your B VC create one property for completion block.
var cellSelectionCallBackHandler: ((DataModel, Int) -> Void)?
Set the cellSelectionCallBackHandler property from VC A.
VCObjectB.cellSelectionCallBackHandler = { (data, index) in
// Use your data here
}
From VC B call the completion handler on selecting cell like this
cellSelectionCallBackHandler?(yourData, index)
Any doubt plz comment.
A delegate method can be used to achieve it. The place/method where you call to dismiss B call the delegate. You can implement this delegate in A.
You can also have a singleton dataHandler class, where you can set and get required properties and can access it from anywhere within your project.

Tableview passing same data to another Tableview

I have a an app that displays players through a Tableview. The user can "add" a new player and input his name, height, weight, picture, etc... and once saved will be added to the Tableview. They can also tap on each player and it takes them to the player detail view controller where it displays all of the information you entered (name, height, weight, throw, bat, position, etc...). This is the current code when a user taps on a player to take them to the player detail page:
var player2 = [Player]()
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detail" {
let playerDetailScreen = segue.destinationViewController as! PlayerDetailViewController
if let selectedPlayerCell = sender as? PlayerListTableViewCell {
let indexPath = tableView.indexPathForCell(selectedPlayerCell)!
let selectedPlayer = player2[indexPath.row]
playerDetailScreen.playereDetail = selectedPlayer
}
}
}
I am trying to implement a tableview inside the view controller where the user adds a new player and enters the information. This table view is where a user can add the teams or years someone has played (I am using table view because the number of entries can vary). My question is what is the best way to go about this?? Right now I have a 2nd class named Teams.swift for this specific table view but is this the best way to do it? The idea is to then display this data in a tableview on the detail page (again, because the entries will vary in number) so how do I pass the data from one tableview to another??
Should I eliminate the [Teams] class and just put it under the [Player] class? Any thoughts would be appreciated!
Firstly, I would recommend using :
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// what happens when user selects a cell
let cell = self.tableView.cellForRowAtIndexPath.indexPath // This will give you cell that was selected
let selectedPlayer = cell.player // This gives you the instance of the player (Benefit of using player class)
self.performSegueWithIdentifier("identifier", sender : selectedPlayer)
// ^ This will automatically call prepareforseque method.
}
Next, inside prepareforsegue method, do this :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detail" {
let playerDetailScreen = segue.destinationViewController as! PlayerDetailViewController
let selectedPlayer = sender as! Player
playerDetailScreen.name = self.selectedPlayer.name
playerDetailScreen.height = self.selectedPlayer.height
... so on.
// So, remember to create property for each name, height, etc.
// inside playerDetailScreen
// This is how you can transfer data
}
}
}

How do I display the data fetched from called view controller into a dynamic tableviewcell of the calling view controller while using unwind segue.?

I have dynamic tableview, wherein one of the cell (duration) when tapped opens another view controller which is a list of duration viz (30 min, 1 hour, 2 hours and so fort). One of the durations when selected should display the selected duration in the first view controller. I am able to pass the data back to first view controller using unwind segue but unable to display the passed value. DOn't know whats missing.
I am displaying the code below:
FIRST VIEW CONTROLLER (CALLING)
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
var cell = tableView.dequeueReusableCellWithIdentifier("durationCell") as! durationTableViewCell
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController,
selectedDuration = durationTableViewController.selectedDuration {
cell.meetingDurationCell.text = selectedDuration
duration = selectedDuration
}
SECOND VIEW CONTROLLER (CALLED)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveSelectedDuration" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedDuration = durationList[index]
}
}
}
}
tableView.dequeueReusableCellWithIdentifier should only be called within tableView:cellForRowAtIndexPath:. It has no use outside this context.
The easiest fix is to just reload the table once you have stored the selected duration:
#IBAction func unwindWithSelectedDuration(segue:UIStoryboardSegue) {
if let durationTableViewController = segue.sourceViewController as? DurationTableViewController {
selectedDuration = durationTableViewController.selectedDuration
tableView.reloadData()
}
}
Note that this assumes you only need one selectedDuration for your whole table, rather than one per row. If you need one per row, I assume you have them stored in an array somewhere, so it is that array that should be updated instead before the reloadData.

NSTableView detect NSTableColumn for selected cell at start of cell edition

I'm trying to programatically get get a a column.identifier for the cell that is being edited. I'm trying to get by registering my NSViewController for NSControlTextDidBeginEditingNotification and when I get the notification I track the data by mouse location:
var selectedRow = -1
var selectedColumn: NSTableColumn?
func editingStarted(notification: NSNotification) {
selectedRow = participantTable.rowAtPoint(participantTable.convertPoint(NSEvent.mouseLocation(), fromView: nil))
let columnIndex = participantTable.columnAtPoint(participantTable.convertPoint(NSEvent.mouseLocation(), fromView: nil))
selectedColumn = participantTable.tableColumns[columnIndex]
}
The problem I have is that the mouse location is giving me the wrong data, is there a way to get the mouse location based on the location of the table, or could there be a better way to get this information?
PS. My NSViewController is NSTableViewDelegate and NSTableViewDataSource, my NSTableView is View Based and connects to an ArrayController which updates correctly, and I could go to my Model object and detect changes in the willSet or didSet properties, but I need to detect when a change is being made by the user and this is why I need to detect the change before it happens on the NSTableView.
This question is 1 year old but I got the same issue today and fixed it. People helped me a lot here so I will contribute myself if someone found this thread.
Here is the solution :
1/ Add the NSTextFieldDelegate to your ViewController :
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate {
2/ When a user wants to edit a cell, he had first to select the row. So we will detect that with this delegate function :
func tableViewSelectionDidChange(_ notification: Notification) {
let selectedRow = self.tableView.selectedRow
// If the user selected a row. (When no row is selected, the index is -1)
if (selectedRow > -1) {
let myCell = self.tableView.view(atColumn: self.tableView.column(withIdentifier: "myColumnIdentifier"), row: selectedRow, makeIfNecessary: true) as! NSTableCellView
// Get the textField to detect and add it the delegate
let textField = myCell.textField
textField?.delegate = self
}
}
3/ When the user will edit the cell, we can get the event (and the data) with 3 different functions. Pick the ones you need :
override func controlTextDidBeginEditing(_ obj: Notification) {
// Get the data when the user begin to write
}
override func controlTextDidEndEditing(_ obj: Notification) {
// Get the data when the user stopped to write
}
override func controlTextDidChange(_ obj: Notification) {
// Get the data every time the user writes a character
}