I am working on an iOS app.
There is a viewController A that perfoms a segue programmatically to a detail viewController D.
On that detail viewController D the user may change some information that should be updated when clicking the back button to viewController A.
On viewController A I have included this function:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineMyCurrentLocation()
print("reloading data")
downloadJSONFavoritos {
print("reloading viewwillappear")
self.collectionViewFavoritos.reloadData()
}
}
My issue is that this function is not always called when back to viewController A, and then the information in A is not updated as it should.
I will create simple example, please adaptate it to your needs.
ViewController A:
class ViewControllerA: UIViewController {
func someMethod() {
let viewControllerD = // creation and displaying logic of your viewController D
viewControllerD.updateCompletionHandler = { [unowned self] in
/// Any updates goes here ...
}
}
}
ViewController D:
class ViewControllerD: UIViewController {
var updateCompletionHandler: (() -> Void)?
/// Just example
#IBAction func triggerUpdateButton(_ sender: UIButton) {
/// Call it, in place where user make changes
updateCompletionHandler?()
}
}
If you have any questions, let me know in the comments.
Related
I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.
I've set up a simple Swift project to try and wrap my head around delegates & protocols. The goal is to pass data between two classes (SendingClass & ReceivingClass). Two buttons in the SendingClass are linked to the delegate which should trigger the Protocol conforming function in the ReceivingClass to execute. This doesn't work unfortunately, I suspect it has to do with where and how I am declaring the ReceivingClass as the delegate.
Appreciate your insights, i'm just starting out!
I've tried setting the delegate in various locations (presently within viewDidLoad, but cant get it to work).
let vc = SendingClass()
vc.statusDelegate = self
SendingClass.swift
import UIKit
protocol StatusDelegate {
func statusChanged(state: Bool, sender: String)
}
class SendingClass: UIViewController {
var statusDelegate : StatusDelegate?
#IBAction func button1Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: true, sender: "Button 1")
}
#IBAction func button2Pressed(_ sender: UIButton) {
statusDelegate?.statusChanged(state: false, sender: "Button 2")
}
}
ReceivingClass.swift
import Foundation
import UIKit
class ReceivingClass: UIViewController, StatusDelegate {
override func viewDidLoad() {
let vc = SendingClass()
vc.statusDelegate = self
}
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
Expected: the ReceivingClass protocol conforming function (func statusChanged) should execute each time the buttons are pressed within the SendingClass.
Actual: Nothing happens
I am using this..
// create extension in your receiving class
extension ReceivingClass: PopUpVCDelegate {
func statusChanged(state: Bool, sender: String) {
print("Sender = \(sender) , State = \(state)")
}
}
// on sending class, when you present your receiving class on any button click
eg.
let resultController = self.storyboard?.instantiateViewController(withIdentifier: "PopUpVCID") as? PopUpVC
resultController?.delegate = self
self.present(resultController!, animated: true, completion: nil)
//or if not have button add on viewdidload in receiving class
// here is full eg
How to get data from popup view controller to custom table view cell?
For protocol and delegate, you use it when u want to bring a value from 2nd VC (presented by 1st or pushed by 1st VC) to 1st VC, which is the original.
From your code, I dont see you presenting or pushing your 2nd VC. that's why it's not working. Hopefully I answered your doubt.
However if you still want to bring a value over from 1st VC to 2nd VC. In second VC, create a variable to receive it
var ReceivedData = String()
then from your first VC, when u are going to push it,
let vc = SendingClass()
vc.ReceivedData = "Whatever you want it to receive"
If you're using storyboard segues, maybe the view controller is instantiated from there so probably you have to use the prepareForSegue and get the destination view controller (which is already instantiated for you) in the ReceivingClass view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if let destination = segue.destination as? SendingClass {
destination.delegate = self
}
}
Also be careful with delegate patter: the delegate property should be declared as a weak property to avoid retain-cycle
weak var delegate: MyDelegate?
I have one view that is contain some data is reading values from server and when the user click on one button I opened another view as popup view then the user make selection to something from this view then when is clicking on OK button the user must return directly to the previous opened view only with update the text on the clicked button with the selected choice.
I don't know if something like that is possible or not in swift 3, I made everything only I don't know what is the way I can use it to make update only on the text of button without make update for all view after read this value from another view!
Main View:
override func viewDidLoad() {
super.viewDidLoad()
//read saved username
let prefs:UserDefaults = UserDefaults.standard
Savedusername = prefs.object(forKey: "SavedUsername")as! String
self.getUserData()
//after this I read and display all date from server and it's OK
// Do any additional setup after loading the view.
}
I used layout directly to go from first view to another view when click on button.
This is the code of OK clicked button , here I want to read the value of choice variable that is take value in the second view and passing this value to the first view only to update text on the first clicked button
#IBAction func okPressedButton(_ sender: Any) {
//here how I can passing data without using this line that is make update for all previous view
//self.performSegue(withIdentifier: "first_view", sender: self)
self.dismiss(animated: true, completion: nil )
}
Update:
the second view:
protocol MyProtocol {
func updateData(data: String)
}
class CalenderPopUpViewController: UIViewController {
#IBOutlet weak var calenderPopUp: UIView!
#IBOutlet weak var datePickerView: UIDatePicker!
var delegate:MyProtocol?
var selectedDate:String = ""
override func viewDidLoad() {
super.viewDidLoad()
calenderPopUp.layer.cornerRadius = 10
calenderPopUp.layer.masksToBounds = true
// Do any additional setup after loading the view.
}
#IBAction func selectDatePicker(_ sender: Any) {
var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
var strDate = dateFormatter.string(from: datePickerView.date)
self.selectedDate = strDate
print(selectedDate)
}
#IBAction func okPressedButton(_ sender: Any) {
self.delegate?.updateData(data: self.selectedDate)
self.dismiss(animated: true, completion: nil )
}
#IBAction func cancelPressedButton(_ sender: Any) {
self.dismiss(animated: true, completion: nil )
}
}
the First class :
class UserInfoViewController: UIViewController{
.
.
.
.
.
.
.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "read_date_from_calender" {
(segue.destination as! CalenderPopUpViewController).delegate = self
}
}
override func viewDidAppear(_ animated: Bool) {
if(receiveddata != "")
{
print(receiveddata) // I tried to print received data here but without any result
}
}
}
extension UserInfoViewController: MyProtocol {
func updateData(data: String) {
self.receiveddata = data
}
}
If I'm understanding your question correctly you need to use a protocol
Write something like this at the top of the class of your modal view/view controllers:
protocol MyProtocol {
func newDataSelected(data: String)
}
Then somewhere in your presented view class declare a variable like this
var delegate: MyProtocol?
Then when you make a selection call the delegate to pass the data back to your presenting view controller:
self.delegate?.newDataSelected(data: "someData")
When you are presenting the view controller be sure to set the delegate:
func present() {
let modal = ModalViewController()
modal.delegate = self
self.present(modal, animated: true, completion: nil)
}
Finally make sure that you inherit from the protocol in your presenting view controller
extension PresentingViewController: MyProtocol {
func newDataSelected(data: String) {
// Do some stuff
}
}
There are a few ways to do that.
Define your own delegate. Define your own protocol with a sub function onUserDataChanged. Override in your MainView and set as delegate to second view. In second view, you will call self.delegate. onUserDataChanged(data)
Use NotificationCenter to notify the info changes.
Use global variable or UserDefault. In second view's button tap handler, save the info as the global variable defined. In MainView's viewWillAppear, you will read the info and set to button.
class A : UIViewController {
var b = B()
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = false
print("inside view will appear")
print(b.transferText) // Here showing Goku only
}
}
And Second class
class B : UIViewController{
var transferText = "Goku"
...
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.transferText = "vegeta"
}
}
So I have to basically transfer the transferText from class B to A, when i click the UINavigationBar Back button. Any help would be appreciated .
Use static variable to do that
class B : UIViewController{
static var transferText = "Goku"
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
B.transferText = "vegeta"
}
}
in A class Use
print(B.transferText)
or for use after navigation back you can use completion handler , when navigation back just call that completion block with text.
Using delegation pattern would be a nice solution for passing data from child to parent (In your case - Class B to Class A) as you're saying Class A Has Class B instance and need to post data back to the parent when an event occurs.
1) Declare a protocol in Class B.
import UIKit
protocol ClassBDelegate
{
func didUpdate(strTransferText : String)
}
class B: UIViewController
{
// Should have valid connection from your Interface builder to this function.
#IBAction func navBarBtnUpdateClickked(_ sender: AnyObject)
{
// on Navigation bar click,Update transferText and fire the delegate again.
transferText = "Updated Goku by navigation bar button tap"
didTapNavigationbarButton();
}
var transferText = "Goku" // Default.
var delegate : ClassBDelegate? // Declare a public variable to store delegate.
override func viewDidLoad()
{
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
// Update transferText and fire delegate.
transferText = "Updated Goku by navigation bar button tap"
didTapNavigationbarButton(); // fire delegate from ViewWill appear.
}
private func didTapNavigationbarButton() -> Void
{
// Need to notify Instance of A class from here.
// Using Delegation pattern!
if(delegate != nil)
{
delegate!.didUpdate(strTransferText: transferText);
}
}
}
2) Confirm to ClassBDelegate in Class A and implement the delegate.
// Confirming to ClassB Delegate
class A: UIViewController,ClassBDelegate
{
var b : B?
override func viewDidLoad()
{
super.viewDidLoad()
// Setup a segue in storyboard to move from class A to Class B to get class B Instance.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
b = segue.destination as? B
b?.delegate = self
print("inside Prepare for segue function of Class A")
print(b?.transferText) // Here showing Goku only(Default value).
}
internal func didUpdate(strTransferText: String)
{
print("Updated transferText in Class B " + strTransferText);
}
}
3) There you go,You'll get call backs for Class A instance whenever button pressed in Class B.
you can achieve it by other ways like Observer pattern(NSNotificationCenter) or KVO pattern and even with Singleton pattern(Not applicable always).
Hope this helps to explore the best fit for your requirement.
Happy to help :)
In class B, you declare property for A class
var a = A()
And in the B's viewWillDisappear, put
a.transferText = "vegeta"
(you declared property transferText in A class).
I have the following class below. The idea is it will use a custom Progress Window View Controller to handle progress of various different events. The problem is since this is in a class and not a view controller it's self, I'm not sure how to make the progressWindow actually show up after I instantiate it from the storyboard?
How do I do this? Currently I get an error that the application tried to present model view controller on itself.
import Foundation
import UIKit
class StatusProgress{
static var cancelCode = {}
static var runCode = {}
static var theProgressWindowController = ProgressWindowViewController()
static var returningViewControllerIdentifier = ""
static let storyboard = UIStoryboard(name: "Main", bundle: nil)
static func run(){
// This will run in parralel but on main queue. Has to be on this Queue because it might involve UI
dispatch_async(dispatch_get_main_queue(), {
// Update the UI on the main thread.
StatusProgress.runCode()
});
}
static func cancel(){
dispatch_async(dispatch_get_main_queue(), {
StatusProgress.cancelCode()
dispatch_sync(dispatch_get_main_queue(),{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(returningViewControllerIdentifier)
vc.presentViewController(vc, animated: true, completion: nil)
})
});
}
static func show(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil) //use own instance to show it's self? (throws error! application tried to present modal view controller on itself. Presenting controller is <Inventory_Counter.ProgressWindowViewController: 0x1466ea390>.')
})
}
}
My problem is essentially I need a replacement for this line of code.
theProgressWindowController.presentViewController(theProgressWindowController, animated: true, completion: nil)
I forgot to mention here is the code that runs it inside another view controller.
SyncViewController.swift
import UIKit
class SyncViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func yesSyncButtonAction(sender: UIButton) {
StatusProgress.returningViewControllerIdentifier = "syncWindow"
StatusProgress.runCode = {
print("run code test")
}
StatusProgress.cancelCode = {
print("cancel code test")
}
StatusProgress.show()
}
#IBAction func noSyncActionButton(sender: UIButton) {
tabBarController?.selectedIndex = 1 //assume back to inventory section
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
The biggest problem is that your StatusProgress class is instantiating and showing a view controller. View controllers should instantiate and show other view controllers, model objects should not. So you need to move the logic for presenting the new view controller into you SyncViewController. Then use delegation to communicate to the SyncViewController that the syncing is done.
protocol StatusProgressDelegate {
func statusProgress(status: StatusProgress, shouldShow: Bool)
func statusProgress(status: StatusProgress, shouldCancel: Bool)
}
Your StatusProgress object would have a delegate that conforms to that protocol and call that delegate inside of its show and cancel methods. This means that you need to make the static functions instance methods, and write an initializer for the class so you can instantiate it.
If the view life cycle events are not much important for you, you may just add the view of your progress controller to view of your current controller. or it's even better if you supply the UIView parameter in your show() function.
static func show(attachToView: UIView ){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
attachToView.addSubview(theProgressWindowController.view)
})
}
After all you'd better to remove your progress view from superview
static func cancel(){
dispatch_async(dispatch_get_main_queue(),{
theProgressWindowController = self.storyboard.instantiateViewControllerWithIdentifier("progressWindow") as! ProgressWindowViewController
theProgressWindowController.view.removeFromSuperview()
})
}