Alright, so I have a TableView scene and I'm going to put a button in each of the cells and I need each cell to, when clicked, segue to its own ViewController scene. In other words, I need to know how to connect a button to a scene (The only button I have right now is "milk")
I know how to create an IBAction linked to a button, but what would I put in the IBAction?
I'm a beginner so I need a step-by-step explanation here. I've included a picture of my storyboard. I haven't written any code yet.
If you want to have a button trigger the segue transition, the easiest thing to do is Control+Click from the button to the view controller and choose a Segue option (like push). This will wire it up in IB for you.
If you want to write the code to do this yourself manually, you can do so by naming the segue (there's an identifier option which you can set once you've created it - you still need to create the segue in IB before you can do it) and then you can trigger it with this code:
V2
#IBAction func about(sender: AnyObject) {
performSegueWithIdentifier("about", sender: sender)
}
V3
#IBAction func about(_ sender: Any) {
performSegue(withIdentifier: "about", sender: sender)
}
You can use the delegation pattern. Presuming that you have implemented a custom table cell, you can define a property in its class to hold whatever you think is helpful to identify the row - it can be its index, or (my preferred way) an instance of a class which represents the data displayed in the cell (I'm calling it MyCellData.
The idea is to let the cell notify the table view controller about a tap on that button, passing relevant data about (the data displayed in) the row. The table view controller then launches a segue, and in the overridden prepareForSegue method it stores the data passed by the cell to the destination controller. This way if you have to display details data about the row, you have all the relevant info, such as the details data itself, or an identifier the destination view controller can use to retrieve the data for example from a local database or a remote service.
Define a protocol:
protocol MyCellDelegate {
func didTapMilk(data: MyCellData)
}
then declare a property named delegate in the cell class, and call its didTapMilk method from the IBAction
class MyTableCell : UITableViewCell {
var delegate: MyCellDelegate?
var data: MyCellData!
#IBAction func didTapMilk() {
if let delegate = self.delegate {
delegate.didTapMilk(self.data)
}
}
}
Next, implement the protocol in your table view controller, along with an override of prepareForSegue
extension MyTableViewController : MyCellDelegate {
func didTapMilk(data: MyCellData) {
performSegueWithIdentifier("mySegueId", sender: data)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "mySegueId" {
let vc: MyDestinationViewController = segue.destinationViewController as MyDestinationViewController
vc.data = sender as? MyCellData
}
}
}
Of course you need a data property on your destination view controller for that to work. As mentioned above, if what it does is displaying details about the row, you can embed all required data into your MyCellData class - or at least what you need to retrieve the data from any source (such as a local DB, a remote service, etc.).
Last, in cellForRowAtIndexPath, store the data in the cell and set its delegate property to self:
extension MyTableViewController : UITableViewDataSource {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data: MyCellData = retrieveDataForCell(indexPath.row) // Retrieve the data to pass to the cell
let cell = self.tableView.dequeueReusableCellWithIdentifier("myCellIdentifier") as MyTableCell
cell.data = data
cell.delegate = self
// ... other initializations
return cell
}
}
Use self.performSegueWithIdentifier("yourViewSegue", sender: sender) under your event for handling button's click:
#IBAction func redButtonClicked(sender: AnyObject) {
self.performSegueWithIdentifier("redView", sender: sender)
}
In the above code, redView is the segue identifier.
Related
I am trying to pass value I get from Firebase to another tableView. I get 2 values from Firebase - "Brands" and "Products". I am trying to make like car app. If you click on Ford then new tableView will appear and shows all the Ford models. This is what I've done so far.
like this I get Brands from Firebase:
func parseSnusBrands(){
let ref = FIRDatabase.database().reference().child("Snuses").child("Brands")
ref.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
if let all = (snapshot.value?.allKeys)! as? [String]{
for a in all{
if let products = snapshot.value![a] as? [[String:String]]{
self.snusBrandsArray.append(["key":a,"value":products])
}
}
self.snusBrandsTableView.reloadData()
}
}
})
}
And like this I detect which cell is clicked and print the product that belongs to the clicked Brand:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
print("products at \(indexPath.row) --> \(snusBrandsArray[indexPath.row]["value"])")
}
How to pass the (snusBrandsArray[indexPath.row]["value"]) to new tableView? I tried using segues and looking for tutorials like "How to pas value between viewControllers" but I am out of luck. Right now I have 2 tableViewController.swift files and one tableViewCustomCell.swift file. Do I need some more files?
For send data, first of all declare your variable in 2nd view controller..
var productsValue = [[String:String]]()
and in 1st viewcontroller
var valueTopass = [[String:String]]()
Than in didSelectRowAtIndexPath, take a value in one valueTopass
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("products at \(indexPath.row) --> \(snusBrandsArray[indexPath.row]["value"])")
if let products = snusBrandsArray[indexPath.row]["value"] as? [[String:String]]{
valueTopass = products
performSegueWithIdentifier("toProducts", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "toProducts") {
var viewController = segue.destinationViewController as! SnusProductsTableViewController
viewController.productsValue = valueTopass
print(productValues)
}
}
You need to use Segues to pass the data forward.
To pass data from the current view controller to the next new view controller using segues, first create a segue with an identifier in the relevant storyboard. Override your current view controller's prepareForSegue method. Inside the method check for the segue you just created by its identifier. Cast the destination view controller and pass data to it by setting properties on the downcast view controller.
Setting an identifier for a segue:
Segues can be performed programatically or using button action event set in the storyboard by ctrl+drag to destination view controller.
You can call for a segue programatically, when needed, using segue identifier in the view controller:
func showDetail() {
performSegueWithIdentifier("showDetailingSegue", sender: self)
}
You can configure segue payload in the override version of prepareForSegue method. You can set required properties before destination view controller is loaded.
Swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetailingSegue" {
let controller = segue.destinationViewController as! DetailViewController
controller.isDetailingEnabled = true
}
}
DetailViewController is the name of the second view controller and isDetailingEnabled is a public variable in that view controller.
To expand on this pattern, you can treat a public method on DetailViewController as a pseudo initializer, to help initialize any required variables. This will self document variables that need to be set on DetailViewController without having to read through it's source code. It's also a handy place to put defaults.
Swift
func initVC(isDetailingEnabled: Bool) {
self.isDetailingEnabled = isDetailingEnabled
}
Why not pass the whole dictionary with all the contents from firebase to the new VC using prepare for segue?
And then store the dict in the destinationVC data model?
That should do the trick.
Been trying to figure this out for a while now and after a couple hours of searching for a solution I decided it was time to ask.
I have a tableview that gets populated by custom UITableViewCells and currently when you tap on a cell it takes you to a detail view.
Within the custom cell there is an image, I would like the user to be able to tap on that image and segue to a popover VC that shows the image.
What I'm having trouble with is creating the segue when the image is tapped.
In the file for the custom cell, I've set up a tap gesture recognizer on the image (pTap):
override func awakeFromNib() {
super.awakeFromNib()
let tap = UITapGestureRecognizer(target: self, action: #selector(PostCell.voteTapped(_:)))
let ptap = UITapGestureRecognizer(target: self, action: #selector(PostCell.imageTapped(_:)))
tap.numberOfTapsRequired = 1
ptap.numberOfTapsRequired = 1
voteImage.addGestureRecognizer(tap)
voteImage.userInteractionEnabled = true
featuredImg.addGestureRecognizer(ptap)
featuredImg.userInteractionEnabled = true
}
I also have a function in the custom cell file for the tap:
func imageTapped(sender: UIGestureRecognizer) {
print("image tapped")
}
In my view controller file I've added a segue in did select row at index path:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let post: Post!
if inSearchMode {
post = filteredVenues[indexPath.row]
} else {
post = posts[indexPath.row]
}
print(post?.venueName)
performSegueWithIdentifier("imageTapped", sender: nil)
performSegueWithIdentifier("DetailsVC", sender: post)
}
Also, in the storyboard I have created a segue from the VC that holds the tableview with the custom cells to the VC I'd like to show when the image is tapped.
I've tried several different methods of getting this to work and haven't had any luck, the code you see above are what remains after my many failed attempts. I feel that the function for the tap in the custom cell file and the segue in the VC file are a part of the solution so that is why I have left them in.
Any help would be appreciated. Thanks!
Updates to code from answers below:
Added protocol
protocol ImageSegueProtocol: class {
func imageTapped(row: Int)
}
class PostCell: UITableViewCell {
Added IAB Func
#IBAction func imageTapped(sender: UIGestureRecognizer) {
guard let row = row else { return }
delegate?.imageTapped(row)
print("image tapped func")
}
Declared delegate in the Main VC
weak var delegate:postCell?
Assigned Delgate
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//let post = posts[indexPath.row]
if let cell = tableView.dequeueReusableCellWithIdentifier("PostCell") as? PostCell {
var img: UIImage?
var vImg: UIImage?
postCell?.delegate = self
Added extension function
extension FeedVC: ImageSegueProtocol {
func imageTapped(row: Int) {
if inSearchMode == true {
let object = filteredVenues[row]
performSegueWithIdentifier("imageTapped", sender: object)
print("ext func")
} else {
let object = posts[row]
performSegueWithIdentifier("imageTapped", sender: object)
print("ext func")
}
}
You could do it like this:
Within the cell file, declare a protocol at the top, and then set up some properties within the cell class itself and the delegate behaviour in response to the image being tapped:
protocol SomeCellProtocol: class {
func imageTapped(row: Int)
}
class SomeCell: UITableViewCell {
weak var delegate: SomeCellProtocol?
var row: Int?
#IBAction func imageTapped() {
guard let row = row else { return }
delegate?.imageTapped(row)
}
}
You must then make your ViewController adopt SomeCellProtocol and call the segue within like so:
extension SomeViewController: SomeCellProtocol {
func imageTapped(row: Int) {
//get object from array and call segue here
}
}
And you must set your ViewController as the delegate of your cells by calling:
someCell.delegate = self
and pass the row to the cell:
someCell.row = indexPath.row
within your cellForRowAtIndexPath method of the ViewController.
So, when the button is tapped within the cell (or you can do it with a GestureRecognizer on the ImageView if you want) it will force the delegate (the ViewController) to call its imageTapped function, passing a row parameter which you can use to determine which object in the table (its corresponding data array) should be passed via the Segue.
As OhadM said, create a button and set the background image as the image you want displayed. From there, you don't even need an IBAction. Control-drag from the button to the next view controller and create the segue.
Then, if you want to do any setup before the segue, in the first VC you'd have something like:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "imageButtonPressed" { // Make sure you name the segue to match
let controller = segue.destinationViewController as! SecondVC
controller.someText = "Hi"
controller.someInt = 5
}
}
What you need to do is to create a button instead of the image and this button will hold the actual image:
The rest is easy, ctrl drag an IBAction into your custom cell file.
Now you need to communicate with your View Controller in order to invoke your segue method.
You can achieve that using 2 design patterns:
Using post notification:
Inside your View Controller add an observer:
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(YourViewController.cellSelected(_:)),
name: "CellSelectedNotificationName",
object: nil)
Your method cellSelected inside your View Controller should look like this:
func cellSelected(notification : NSNotification)
{
if let passedObject = notification.object as? YourCustomObject
{
// Do what ever you need with your passed object
// When you're done, you can invoke performeSegue that will call prepareForSegue
// When invoking performeSegue you can pass your custom object
}
}
Inside your CustomCell class at the IBAction method of your button:
#IBAction func buttonTapped()
{
// Prepare the object you want to pass...
NSNotificationCenter.defaultCenter().postNotificationName("CellSelectedNotificationName", object: YourCustomObjectYouWantToPass)
}
In a nut shell, a button inside a cell was tapped, you created an object inside that cell and you pass it to a View Controller using the notification centre. Now, you can segue with your custom object.
NOTE: If you don't need to pass an object you can basically ctrl + drag from your UIButton (at your storyboard) to another View Controller.
Using a delegate that is pointing to the View Controller.
Good luck.
I have a table view with a list of events. I now am trying to create a dynamic view that can show details about all of those events based on the event tapped on. Two questions I have are how do I create a generic segue that I can use for any table cell to go to the same view, and then in that view how do I access the cell that brought the user to the view?
You would use didSelectRowAtIndexPath. First, create a variable eventToPass of type Event (or whatever your class is called) on your ViewController. Then, you should get the object that represents your data, then pass that to your new UIViewController. Something like:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let event = eventsArray[indexPath.row]
self.eventToPass = event
self.performSegueWithIdentifier("EventSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "EventSegue") {
var detailVC = segue.destinationViewController as EventDetailViewController
detailVC.event = eventToPass
}
}
This assumes that you have a ViewController called EventDetailViewController that has a property called event. From here, you can access any of the event's details on your new ViewController.
I have a TableViewController that has static cells inside groups. I want to perform segue from some of the static cells, but also somehow know which cell triggered the segue in the segued TableViewController.
I ctrl + dragged from each cell to the destination TVC, added segue identifiers and created a class for TVC.
I this approach, but the segue doesn't work. It doesn't print XOX either
class MainTableViewController {
viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
print("XOX")
if segue.identifier == "MainToA" {
if let destination = segue.destinationViewController as? DetailTableViewController {
destination.page = "A"
}
}
if segue.identifier == "MainToB" {
if let destination = segue.destinationViewController as? DetailTableViewController {
destination.page = "B"
}
}
}
class DetailTableViewController {
var page = String()
viewDidLoad() {
print(page)
}
}
Am I missing out something or completely out of track? Why doesn't the segue work?
I tried and the segues work correctly with connection to individual UITableViewCell
You can see in the image the triggered segue part
Please make sure you have connected the segue from the cell instead of any subview inside it.
Edit
From the chat it appears you have a UITapGesture which takes up the touch of the view.
To avoid this i would suggest to removeTapGesture and use tableView didSelectRow methods to detect touches on view
Did you subclass UITableViewController? Did you assign that subclass in Interface Builder? Did you make sure NOT to implement override func numberOfSectionsInTableView(tableView: UITableView) -> Int
so that your static cells actually appear?
When you ctrl+dragged did you link up Triggered Segues by Selection, not Accessory action?
Try enabling the Selection property in your Table View Static Content
Attributes inspector, as shown in the picture here.
I have this:
MyTableViewController (inherits from UITableViewController)
It has a dynamic tableview with a few cells (foo, bar, qux)
MyViewController (inherits from UIViewController)
There are some "show" segues from this controller to other view controllers
It has a UIContainerView that embeds MyTableViewController
A picture speaks a thousand words:
When a certain cell is selected, I want to perform a segue of the parent view (MyViewController)
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (indexPath.section == 1 && indexPath.row == 1) {
self.WHAT.performSegueWithIdentifier("someShowSegue1", sender: self)
}
}
Is it possible? what should I use in «WHAT»?
In the prepareForSegue: for your embedded segue set the viewController in a new property in your tableViewController, let's name it parentController. And then you'll have just to call self.parentController.performSegueWithIdentifier().
EDIT: But first of all, maybe you can use the existing parentViewController if it contains the embedding view controller.
You may want to consider using delegation to solve this problem since the child tableView doesn't seem like it should be responsible for the segue. For example:
// MyViewController
class MyViewController: UIViewController, MyTableViewControllerDelegate {
func selectedMyTableViewControllerCell(cell: UITableViewCell) {
// ... check cell type or index or whatever
self.performSegueWithIdentifier("someValueFromCellType", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == myTableViewControllerIdentifier {
if let vc = segue.destinationViewController as MyTableViewController? {
vc.delegate = self
}
}
}
}
// MyTableViewController
protocol MyTableViewControllerDelegate: class {
func selectedMyTableViewControllerCell(cell: UITableViewCell)
}
class MyTableViewController: UITableViewController {
weak var delegate: MyTableViewControllerDelegate?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// ... get the cell
delegate?.selectedMyTableViewControllerCell(cell)
}
}
No need to create a property. Just this
self.parent?.performSegue(withIdentifier: "ID", sender: self)
SWIFT 4
Swift 4 no longer has parentViewController. You must use parent to access the parent object and is an optional so be sure to check for nil where necessary.
self.parent?.performSegue(withIdentifier: "IdentifierHere", sender: self)
Hook your segue up to the embedded table view controller's cell instead. You can use different segues per cell prototype. This saves you from checking index paths or even implementing didSelectRow at all.
Segue is defined from one view controller to another and is only invoke from the view controller in which it is defined. So you would need to store the reference of the parentViewController.
Like from MyViewController
if ([segueName isEqualToString: #"embedseg"]) {
MyTableViewController * tblViewController = (MyTableViewController *) [segue destinationViewController];
tblViewController.parentController=self; //Storing reference of parentViewController i.e MyViewController
}
Now you can simply invoke segues like
self.parentController.performSegueWithIdentifier("someShowSegue1", sender: self)
Hope this helps