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() {
}
Related
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
}
}
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)
}
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)
}
I am making an app that uses a set of drill-down tables, each with their own TableViewController, so my storyboard is set up with a MainViewController that has a segue to a TableViewController (MainTable) which has a segue to another TableViewController (SubTable). I created a protocol and now I'm trying to assign the SubTable as the delegator and MainTable as the delegate. All of the solutions I've found only work when the delegator is called (or segued to) directly from the delegate class, in which case you can use SubTable.delegate = selfin the prepareForSegue method, but that doesn't work if SubTable is called(created) from within the delegate class (MainView). Sorry if this is confusing, I will be happy to clarify any of it. Here is an example of what I want:
class MainViewController: UIViewController, delegate {
override func viewDidLoad() {
super.viewDidLoad()
SubTable.delegate = self //Doesn't work
}
func acceptData(data: String!) {
print(data)
}
}
the SubTable.delegate = self line doesn't work because SubTable hasn't been created yet, but if I create it beforehand using something like let instance = SubTable() and then instance.delegate = self then it still dines't work because the segue from MainTable to SubTable creates a new, separate instance of SubTable.
protocol delegate {
func acceptData(data: String!)
}
class MainTable: UITableViewController {
}
class SubTable: UITableViewController {
var delegate: delegate?
func sendData() {
self.delegate?.acceptData(data: "some text")
}
}
You may pass your delegate to the MainTable and assign it there when creating SubTable. Create a field in your MainTable to store it
var subTableDelegate : delegate?
in MainViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! MainTable
destinationVC.subTableDelegte = self
}
in MainTable:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! SubTable
destinationVC.delegate = self.subTableDelegate
}
The following code should show two ways to pass information from an embedded controller (UICollectionView) back to a detailed view controller using either the Responder Chain OR delegate approach. Both approaches use the same protocol, and delegate method. The only difference is if I comment out the delegate?.method line in didSelectItemAtIndex path, the Responder Chain works. BUT, if I comment out the Responder Chain line in the didSelectItemAtIndex method, the uncommentented delegate? property doesn't call the method, and remains nil.
Protocol defined and included above DetailViewController. Needed for both approaches.
protocol FeatureImageController: class {
func featureImageSelected(indexPath: NSIndexPath)
}
Delegate property declared in the custom UICollectionViewController class, which is only needed for delegate approach.
class PhotoCollectionVC: UICollectionViewController
{
weak var delegate: FeatureImageController?
In DetailViewController, an instance of PhotoCollectionVC() is created, and the delegate property set to self with the delegate protocol as type.
class DetailViewController: UIViewController, FeatureImageController
{...
override func viewDidLoad() {
super.viewDidLoad()
let photoCollectionVC = PhotoCollectionVC()
photoCollectionVC.delegate = self as FeatureImageController
Within the collection view controller's didSelectItemAtIndexPath method, pass back the selected indexPath via either the Responder Chain (commented out) OR the delegate to the featureImageSelected method in the DetailVC.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
// if let imageSelector = targetForAction("featureImageSelected:", withSender: self) as? FeatureImageController {
// imageSelector.featureImageSelected(indexPath)
// }
self.delegate?.featureImageSelected(indexPath)
}
An instance of elegate method in DetailViewController. Needed for both.
func featureImageSelected(indexPath: NSIndexPath) {
record?.featureImage = record?.images[indexPath.row]
self.configureView()
}
Why would the Responder Chain approach work, but the delegate not?
There are no compiler or run time errors. Within the didSelectItemAtIndexPath method, the delegate always returns nil and nothing prints from the delegate method.
Your responder code calls a featureImageSelected on self:
self.featureImageSelected(indexPath)
but the delegate code calls featureImageSelected on the delegate:
self.delegate.featureImageSelected(indexPath)
Which would be the DetailVC's delegate, not the collectionViews delegate. Im not really sure what your code is doing, but you probably want something like
collectionView.delegate?.featureImageSelected(IndexPath)
which looks like it would just end up being
self.featureImageSelected(indexPath)
The error in the question is where, in the conforming class, "an instance of PhotoCollectionVC() is created, and the delegate property set to self". In viewDidLoad, that just creates another instance with an irrelevant delegate property that will never be called. The delegate property of the actual embedded PhotoCollectionVC needs to be assigned to self - in order for the two VCs to communicate. This is done from within the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
...
let controller = (segue.destinationViewController as! PhotoCollectionVC)
...
controller.delegate = self
}
}
}
The rest of the example code is fine.
Here is a super simple example of delegation from an embedded container to its delegate VC. The embedded container simply tells the VC that a button has been pressed. The story board is just a VC with a container in it and a text outlet. In the container VC, there is just a button. And the segue has an identifier.
The code in the delegate ViewController is:
protocol ChangeLabelText: class
{
func changeText()
}
class ViewController: UIViewController, ChangeLabelText
{
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
myLabel?.text = "Start"
}
func changeText()
{
myLabel?.text = "End"
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "feelTheBern"
{
let secondVC: myViewController = segue.destinationViewController as! myViewController
secondVC.delegate = self
}}
}
The code in the delegating View Controller, myViewController, is:
class myViewController: UIViewController
{
weak var delegate: ChangeLabelText?
#IBAction func myButton(sender: AnyObject)
{
print("action")
delegate?.changeText()
}
override func viewDidLoad() {
super.viewDidLoad()
}
}