Provide action for button in the xib file using swift3 - ios

PROVIDED: I have a button in my xib
The swift file associated with this is also attached.
ISSUE: this cell has a button that need to display a ViewController on the button click. This cell is attached to the table view in another ViewController. I want to implement an action on the button "BOOK" so as on clicking the new view controller should open. i am not able to do this can any one suggest me something that i should do?
CODE:
import UIKit
class HotelBookingCell: UITableViewCell {
#IBOutlet weak var BookbtnOutlet: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
BookbtnOutlet.layer.cornerRadius = 7
// Initialization code
}
#IBAction func bookbtn1(_ sender: Any) {
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

remove the following code in tableviewcell class
/*
#IBAction func bookbtn1(_ sender: Any) {
} */
and add into your UIviewcontroller cellForRowAtIndexPath
cell. BookbtnOutlet.tag = indexpath.row
cell. BookbtnOutlet.addTarget(self, action: #selector(self. bookbtn1(_:)), for: .touchUpInside);
And anywhere in the same UIVeiwController define the function as below
func bookbtn1(_ sender : UIButton){
// call your segue code here
}

One of possible solutions it to create a protocol for this:
protocol HotelBookingCellDelegate: class {
// you can add parameters if you want to pass. something to controller
func bookingCellBookButtonTouched()
}
then is you cell class
class HotelBookingCell: UITableViewCell {
// add a propery
public weak var delegate: HotelBookingCellDelegate?
#IBAction func bookbtn1(_ sender: Any) {
delegate?.bookingCellBookButtonTouched()
}
}
in your cellForRowAtIndexPath
cell.delegate = self
after that in you controller where you signed for this protocol, implement it
extension YourViewController: HotelBookingCellDelegate {
func bookingCellBookButtonTouched() {
// Do whatever you want
}
}

You can create a delegate protocol in the cell class and then set the delegate equal to viewcontroller where tablview cell will show up. Then on click the delegate function will be called and you will get the action the view controller where you can push or pop a view controller or any other action you want.
Sample code - Cell Class
protocol ButtonDelegate {
func buttonClicked()
}
weak var delegate : ButtonDelegate?
#IBAction func bookbtn1(_ sender: Any) {
delegate?.buttonClicked()
}
Now in View Controller conform to the protocol - "ButtonDelegate", set
cell.delegate = self
and then implement the method "buttonClicked()"
You will get the action in buttonClicked() when button is clicked.

There are two possible solution here
1) You can add target for this button in cellForRowAtIndexPath like below code
cell.bookbtn1.tag = indexPath.row
cell.bookbtn1.addTarget(self, action: #selector(self. bookbtn1(sender:)), for: .touchUpInside)
2) another solution is in your main view controller you can add Notification center observer in viewDidLoad like this
NotificationCenter.default.addObserver(self, selector: #selector(self.bookbtn1ClickedFromCell), name: NSNotification.Name(rawValue: BUTTON_CLICK), object: nil)
and implement method and navigate in another view controller from this method
func bookbtn1ClickedFromCell()
{
//navigate to another vc
}
and in action method that you implemented in UITableViewCell file post this notification like this
#IBAction func bookbtn1(_ sender: Any) {
NotificationCenter.default.post(name: Notification.Name(rawValue: BUTTON_CLICK), object: self)
}
so it will called bookbtn1ClickedFromCell in your main view controller from this you can navigate to another view controller
you should remove observer in viewWillDisappear or in deinit method
deinit {
NotificationCenter.default.removeObserver(self)
}
or
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self)
}

Related

How to inherit custom UITableViewCell to just change action methods

In my app i have custom UITableViewCell A who is owner of the xib file A. Now i need to use all of the functionality of A but need to change buttons action. Is there any way to do this by inheriting A to a new class B.
Here is an approach
class ACell: UITableViewCell {
#IBAction func doSomething(_ sender: Any) {
// do action A
}
}
class BCell: ACell {
#IBAction override func doSomething(_ sender: Any) {
// do action B
}
}
// demo parent controller
class ViewController: UIViewController {
let bCell = BCell() // keep reference to cell
override func viewDidLoad() {
super.viewDidLoad()
// assuming A (substitute real name below) xib is located in
// application main bundle, load it as linked to BCell instance
Bundle.main.loadNibNamed("A_Xib_Name", owner: bCell)
}
}

Best way to reload vc table view backing from another vc

I'm trying to reload table view in Calculation controller, pressing back navigation button on Setup controller (red arrow on screenshot).
Which is the best way to do it?
Thanks !
In a navigation controller its's pretty easy. In Swift the most efficient way is a callback closure, it avoids the overhead of protocol/delegate.
In SetupController declare a callback property, a closure with no parameter and no return type
var callback : (() -> Void)?
and call it in viewWillDisappear. viewWillDisappear is allways called when the back button is pressed.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
callback?()
}
In CalculationController assign the callback in prepare(for
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
...
let setupController = segue.destination as! SetupController
setupController.callback = {
self.tableView.reloadData()
}
Using Delegate Pattern
Create delegate with some method for second ViewController. Implement this protocol to first ViewController and when this method is called, reload UITableView data (in overriden prepare(for:sender:) set delegate of second ViewController to self). When second ViewController will disappear, call method on delegate variable of second ViewController.
Now when you're able to use delegates, you can easily add parameter to delegate's method and pass data from second to first ViewController.
protocol SecondVCDelegate: class { // define delegate protocol
func controllerDismissed()
}
class ViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.delegate = self
}
}
}
extension ViewController: SecondVCDelegate {
func controllerDismissed() { // this is called when you call delegate method from second VC
tableView.reloadData()
}
}
class SecondViewController: UIViewController {
weak var delegate: SecondVCDelegate? // delegate variable
override func viewWillDisappear(_ animated: Bool) {
delegate?.controllerDismissed() // call delegate's method when this VC will disappear
}
}
An easy solution is to reload the tableView, when the view is going to appear again.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Alternative solutions could be to implement unwindSegue or delegation.
to achieve this you have multiple solutions first of all you have to know what is the best to use by your case,
1- are you passing data back to the CalculationVC
2- do you just need to reload the CalculationVC each time it appears ?
for the first case you use what called Delegates in swift.
for the second case you can use a life-cycle function that is called viewWillAppear() in the ViewController.
for the Delegate case you can find tons of articles online this one recommended for newbies !
and for the second case just use this code.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
Try this code
protocol VC2Delegate: class {
func viewController(_ myVC2: VC2?, didFinishEditingWithChanges hasChanges: Bool)
}
class VC2 {
private weak var: VC2Delegate? delegate?
weak var: VC2Delegate? delegate?
#IBAction func finishWithChanges() {
delegate.viewController(self, didFinishEditingWithChanges: true)
}
#IBAction func finishWithoutChanges() {
delegate.viewController(self, didFinishEditingWithChanges: false)
}
}
//VC1: implement the VC2Delegate protocol
class VC1: VC2Delegate {
var: Bool _needsReload?
func awakeFromNib() {
super.awakeFromNib()
needsReload = true
}
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadTableIfNeeded()
}
#IBAction func displayVC2() {
}

Swift - Protocol Delegate from TableView not working - Present Modally

I have one view controller with a button that when clicked goes to a second view controller with a table view. The two scenes are linked through storyboard, as Present Modally. I want the text of the button in the first view controller to change when a row is selected in the table view of the second view controller.
Is there a reason why the button is not changing when the cell is selected?
Any help would be greatly appreciated, I'm still a beginner with Swift.
Below are the relevant lines of code:
View Controller 1
class ChartsViewController: UIViewController {
#IBOutlet weak var titleButton: UIButton!
}
extension ChartsViewController: ModalDelegate {
func didSelectValue(value: String) {
self.titleButton.setTitle(value, for: .normal)
}
}
View Controller 2
protocol ModalDelegate: class {
func didSelectValue(value: String)
}
class DashboardModalViewController: UIViewController {
var delegate: ModalDelegate?
}
extension DashboardModalViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let rowTitle = self.rows[indexPath.row].text
print (rowTitle)
delegate?.didSelectValue(value: rowTitle)
dismiss(animated: true, completion: nil)
}
}
I think you need to set the second controller's delegate in View Controller 1's prepare method for segue like:
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dashboardModalViewController = segue.destination as? DashboardModalViewController {
dashboardModalViewController.delegate = self
}
}

perform segue from UIView

I have ViewController and there is UIView in it.
This UIView has separate class myView and there are many UI elements - one of them is CollectionView.
What I want is to perform segue when one of collection elements in myView is selected. But when I try to add line
performSegue(withIdentifier: "myIdintifier", sender: self)
to collection's view didSelectItemAt method I get error
Use of unresolved identifier 'performSegue'
And I understand that this is because I do it inside class that extends UIView and not UIViewController.
So how can I perfrom segue in this case? And also how can I prepare for segue?
Here I am going to evaluate it in step by step manner.
Step - 1
Create custom delegate using protocol as below snippet will guide you on your custom UIView. protocol must exist out of your custom view scope.
protocol CellTapped: class {
/// Method
func cellGotTapped(indexOfCell: Int)
}
Don't forgot to create delegate variable of above class as below on your custom view
var delegate: CellTapped!
Go with your collection view didSelect method as below
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if(delegate != nil) {
self.delegate.cellGotTapped(indexOfCell: indexPath.item)
}
}
Step - 2
Let's come to the your view controller. give the CellTapped to your viewcontroller.
class ViewController: UIViewController,CellTapped {
#IBOutlet weak var myView: MyUIView! //Here is your custom view outlet
override func viewDidLoad() {
super.viewDidLoad()
myView.delegate = self //Assign delegate to self
}
// Here you will get the event while you tapped the cell. inside it you can perform your performSegue method.
func cellGotTapped(indexOfCell: Int) {
print("Tapped cell is \(indexOfCell)")
}
}
Hope this will help you.
You can achieve using protocols/delegates.
// At your CustomView
protocol CustomViewProtocol {
// protocol definition goes here
func didClickBtn()
}
var delegate:CustomViewProtocol
#IBAction func buttonClick(sender: UIButton) {
delegate.didClickBtn()
}
//At your target Controller
public class YourViewController: UIViewController,CustomViewProtocol
let customView = CustomView()
customView.delegate = self
func didClickSubmit() {
// Perform your segue here
}
Other than defining protocol, you can also use Notification.
First, extent nonfiction.name:
extension Notification.Name {
static let yourNotificationName = Notification.Name(“yourNotificationName”)
}
Then right where you want to perform segue but can’t in your custom UIView:
NotificationCenter.default.post(name: .yourNotificationName, object: self)
Finally, you can listen to the notification in your viewControllers:
private var observer: NSObjectProtocol?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
observer = NotificationCenter.default.addObserver(forName: .yourNotificationName, object: nil, queue: nil) {notification in
self.performSegue(withIdentifier:”your segue”, sender: notification.object}
Don’t forget to remove it:
override func viewWillDisappear(_ animated: Bool){
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(observer)
}

Segue from UITableViewCell by tapping on an image inside of cell

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.

Resources