I have to view controllers, from first controller's menu item click, a segue is performed by 'Present Modally' Segue which will open the second viewController (acting as a menu) when i press Refresh button on the second VC, it is calling the method from the first VC To refresh its data and load it on tableView. the problem is it is call the method from second to first VC but is returning nil on this line.
self.tableView.dataSource = self
i think it is because the first VC is not active properly as it is in the background ( the second VC's background is transparent so that i can see the first VC at the back ). i'm new to swift and i don't know how to deal with it.
EDIT: This is the refresh method
func refresh() {
// self.dismiss(animated: true, completion: nil)
// present(DayView_Controller, animated: true, completion: nil)
// self.dismiss(animated: true, completion: nil) // it is dismissing the first view controller instead of second
////////
Extensions.ShowAlert(self.pwait, sender: self) // here it causing the exception on viewDidLoad
let defaults = UserDefaults.standard
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MM/d/yyyy"
let finaldate = formatter.string(from:date)
if Reachability.isConnectedToNetwork(){
// all the services are being call and getting reloaded on tableview
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self // exception here
}
2nd EIDT: now i'm dismissing the 2nd VC on the refresh click and call the method from 1st VC on the completion, still getting the same error.
#IBAction func Refresh_CLick(_ sender: Any) {
self.dismiss(animated: true, completion: {
DayView_Controller().refresh()
})
// DayView_Controller().refresh()
}
I believe, you need to do something like this:
1) Create protocol:
protocol RefreshDelegate {
func dataChanged(data: [Items])
}
2) In your second view controller add:
var delegate: RefreshDelegate ?
if let del = delegate {
del.dataChanged(data: items) // your refreshed data
}
3) In your first view controller add extension:
extension FirstViewController: RefreshDelegate {
func dataChanged(data: [Items]) {
// update your tableView data -> then reload
}
}
Something like this. You should understand main idea.
Hope it helps
Use Delegate to refresh data
protocol RefreshDelegate: class {
func refreshData(data: Int)
}
In your first view controller add delegate
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, RefreshDelegate {
In your prepare for segue method set delegate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "go" {
let viewController:SecondViewController = segue.destination as! SecondViewController
viewController.refreshDelegate = self
}
}
// Delegate
func refreshData(data: Int) {
tableData = data
self.tableView.reloadData()
}
In second view controller add variable for delegate
weak var refreshDelegate: RefreshDelegate?
Now call refresh delegate from second view controller
#IBAction func refreshTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: {
self.refreshDelegate?.refreshData(data: 10)
})
}
Related
i am new to iOS here is my question:
I have a saveCardViewController (Presented Modally) with some textFields and Save button.
#IBAction func Save(_ sender: UIButton) {
date = datePicker.date
try! realm.write() {
sessionCard.pokerType = pokerTypeSegment.titleForSegment(at: pokerTypeSegment.selectedSegmentIndex)!
date = dateFormatter.string(from: date)
sessionCard.handsPlayed = Int(handPlayedTextlabel.text!) ?? 0
sessionCard.moneyIn = Int(moneyInTextLabel.text!) ?? 0
sessionCard.moneyOut = Int(moneyOutTextLabel.text!) ?? 0
sessionCard.timePlayed = Int(timePlayedTextLabel.text!) ?? 0
sessionCard.sortDate = date
realm.add(sessionCard)
}
dismiss(animated: true, completion: nil)
}
How can I reloadData() on my main ViewController, after Save button is pressed and saveCardViewController is dismissed.
Thanks!
EDIT # 1:
Thank you #davidev for your answer,I made changes but still does not update
My ViewController With TableView:
class SessionViewController: BackgroundViewController, RefreshViewDelegate {
func refreshView() {
tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
My ViewController with data and Save button:
protocol RefreshViewDelegate {
func refreshView()
}
class AddSessionViewController: UIViewController, UITextFieldDelegate {
var delegate: RefreshViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func TEST2(_ sender: UIButton) {
delegate?.refreshView()
dismiss(animated: true, completion: nil)
}
You can use delegate pattern to achieve this.
Declare your Refresh Protocol like this:
protocol RefreshViewDelegate {
func refreshView()
}
Make your parent view conform to this protocol and implement refreshView() with your custom refresh action. Also make sure to set the delegate of the child view to self.
Inside saveCardViewController declare your delegate variable
var delegate : RefreshViewDelegate?
And call the delegate action inside your IBaction
delegate?.refreshView()
Edit:
I just saw your updated code. As you are using Storyboard segues, you still have to set the delegate via code. In your main view controller add the function:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
if let viewController = segue.destinationViewController as? AddSessionViewController
{
viewController.delegate = self
}
}
I have set a "Show as popover" segue between a UIView (A) and another UIView (B) (embed in a Navigation Controller) activated on a button's clic.
i am trying to pass datas back from (B) to (A) when i dismiss it (i want to keep the popover animation on both ways).
I have tried many methods i found mostly here, on Stackoverflow, but as of now i never successfully retrieved my data on (A).
I tried Delegates and protocols as well as other simpler methods. The last in date is the following one:
In (A), i just try to print the variable that should be storing the datas in ViewWillAppear :
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var testValue:String = ""
override func viewWillAppear(_ animated: Bool) {
print(testValue) // print is empty
super.viewWillAppear(animated)
}
}
In (B), i dismiss the popover and try to send the datas back on a button clic :
class SearchFilterViewController: UIViewController {
#IBAction func DismissPopoverOnClic(_ sender: Any) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! SearchBarsController
presenter.testValue = "Test"
print("success") //never called
}
self.dismiss(animated: true, completion: nil)
}
}
on (B) i'd like to set up some filter that i'd use on (A) to present search results in a tableview. But actually the testValue's value is always blank.
oky so you can do it using unwind segue here is sample project :
sample projecct
process :
Add this method to SearchBarsController below viewWillAppear
#IBAction func unWindFromFilterViewController(_ sender: UIStoryboardSegue) {
}
Than go to Storyboard and go to SearchFilterViewController and then cntrl + Drag from DismissPopoverOnClic to top of the exit button then select unWindFromFilterViewController .
Than this the SearchFilterViewController write this method for passing data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ViewController {
destVC.testValue = "Test"
}
}
You will get your desired data back . thanks
When passing back data to a viewController, the most efficient way to implement it using delegate
protocol SearchFilterViewControllerDelegate {
func setTextValue(string : String)
}
class SearchFilterViewController: UIViewController {
var delegate : SearchFilterViewControllerDelegate?
#IBAction func DismissPopoverOnClic(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
delegate?.setTextValue(string : "Test Value")
}
}
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var filterViewController : SearchFilterViewController?
func popup() {
// your pop up code and init filterViewController
filterViewController.delegate = self **//without this line, the delegate will be nil, no nothing will happen.**
}
}
extension SearchBarsController : SearchFilterViewControllerDelegate {
func setTextValue(string : String) {
print(string)
}
}
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}
I used this code here to pass data from first view controller to the second view controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? secondViewController {
vc.showPageType = self.checkEdit
}
But the problem is that in the second view controller I have text field that when user fill that text field and push the button submit the secondViewController will be dismiss with this method
dismiss(animated: false, completion: nil)
and now I can't use perform segue method to pass textfield text to the first view controller how can I do that in swift4?
Add to your secondViewController source code file:
protocol SecondViewControllerDelegate {
func submitButtonPushedWithText(_ text: String)
}
Add to class secondViewController property:
var delegate: SecondViewControllerDelegate?
Then conform your first controller to SecondViewControllerDelegate and implement method submitButtonPushedWithText(:):
class FirstViewController: UIViewController, SecondViewControllerDelegate {
func submitButtonPushedWithText(_ text: String) {
// use text from textField of second controller
}
}
Also setup delegate property of second controller before presenting:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? secondViewController {
vc.showPageType = self.checkEdit
// setup delegate
vc.delegate = self
}
Now you can call method submitButtonPushedWithText(_ text: String) in your Second controller just before calling dismiss(animated: false, completion: nil):
func submitButtonPushed() {
delegate?.submitButtonPushedWithText(textField.text!)
dismiss(animated: false, completion: nil)
}
I have two UIViewController, when I click a button, it goes from the first view controller to the second one. And before that, I animated a UIView to move to another place. After dismissing the second View Controller, I want to move the UIView in the first view controller back to where it originally was. However, when I call a function from the second View Controller to animate the UIview in the first view controller after dismissing the second one, It could not get the UIView's properties, and cannot do anything with it. I think because the first UIViewController is not loaded yet. Is that the problem? And How should I solve this?
There are two solutions you can either use swift closures
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.callbackClosure = { [weak self] in
print("Do your stuff")
}
self.navigationController?.pushViewController(secondController, animated: true)
}
}
//----------------------------
class SecondController: UIViewController {
var callbackClosure: ((Void) -> Void)?
override func viewWillDisappear(_ animated: Bool) {
callbackClosure?()
}
}
or you can use protocols
class FirstViewController: UIViewController {
#IBAction func start(_ sender: Any) {
guard let secondController = self.storyboard?.instantiateViewController(withIdentifier: "SecondController") as? SecondController else { return }
secondController.delegate = self
self.navigationController?.pushViewController(secondController, animated: true)
}
}
extension ViewController : ViewControllerSecDelegate {
func didBackButtonPressed(){
print("Do your stuff")
}
}
//--------------------------
protocol SecondControllerDelegate : NSObjectProtocol {
func didBackButtonPressed()
}
class SecondController: UIViewController {
weak var delegate: SecondControllerDelegate?
override func viewWillDisappear(_ animated: Bool) {
delegate?.didBackButtonPressed()
}
}
You can try to use a closure. Something like this:
class FirstViewController: UIViewController {
#IBOutlet weak var nextControllerButton: UIButton!
private let animatableView: UIView = UIView()
private func methodsForSomeAnimation() {
/*
perform some animation with 'animatableView'
*/
}
#IBAction func nextControllerButtonAction() {
// you can choose any other way to initialize controller :)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let secondController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondController.callbackClosure = { [weak self] in
self?.methodsForSomeAnimation()
}
present(secondController, animated: true, completion: nil)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var dismissButton: UIButton!
var callbackClosure: ((Void) -> Void)?
#IBAction func dismissButtonAction() {
callbackClosure?()
dismiss(animated: true, completion: nil)
/*
or you call 'callbackClosure' in dismiss completion
dismiss(animated: true) { [weak self] in
self?.callbackClosure?()
}
*/
}
}
When you present your second view controller you can pass an instance of the first view controller.
The second VC could hold an instance of the first VC like such:
weak var firstViewController: NameOfController?
then when your presenting the second VC make sure you set the value so it's not nil like so:
firstViewController = self
After you've done this you'll be able to access that viewControllers functions.
iOS 11.x Swift 4.0
In calling VC you put this code ...
private struct Constants {
static let ScannerViewController = "Scan VC"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Constants.ScannerViewController {
let svc = destination as? ScannerViewController
svc?.firstViewController = self
}
}
Where you have named the segue in my case "Scan VC", this is what it looks like in Xcode panel.
Now in scan VC we got this just under the class declaration
weak var firstViewController: HiddingViewController?
Now later in your code, when your ready to return I simply set my concerned variables in my firstViewController like this ...
self.firstViewController?.globalUUID = code
Which I have setup in the HiddingViewController like this ...
var globalUUID: String? {
didSet {
startScanning()
}
}
So basically when I close the scanning VC I set the variable globalUUID which in term starts the scanning method here.
When you are saying it could not get the UIView's properties it's because you put it as private ? Why you don't replace your UIView in the first controller when it disappears before to go to your secondViewController. I think it's a case where you have to clean up your view controller state before to go further to your second view controller.
Check IOS lifecycle methods : viewWillDisappear or viewDidDisappear through Apple documentation and just do your animation in one of these methods.
Very simple solution actually... Just put your animation in the viewDidAppear method. This method is called every time the view loads.
class firstViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// insert animation here to run when FirstViewController appears...
}
}