My work is
1 .Passing data from first view controller -> second view controller using pushViewController: Success
2 .Passing data from second view controller -> First View Controller using popViewController: Success //used delegate protocol for return data
3 .Passing data from third view controller -> First View Controller : Error
Code :
ViewController.swift
import UIKit
class ViewController: UIViewController,secondViewDelegate,thirddelegate {
#IBOutlet var Firstoutput: UILabel!
#IBOutlet var inputField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
func popdata(value: String) { //second view controller delegate protocol defintions
Firstoutput.text = "\(value)"
}
func thirdView(datas:String) //third view controller delegate protocol definition
{
print(datas)
}
#IBAction func pushBtn(_ sender: Any) { //first view button action
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcFirst = storyboard.instantiateViewController(withIdentifier: "second") as! SecondViewController //secondview
vcFirst.Secondtext = inputField.text
vcFirst.delegate = self //second view delegate intiate
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self //third view delegate intiate
navigationController?.pushViewController(vcFirst, animated: true)
}
}
SecondViewController.swift
import UIKit
//protocol for passing data when pop
protocol secondViewDelegate {
func popdata(value:String)
}
class SecondViewController: UIViewController {
#IBOutlet var secondOutputField: UILabel!
#IBOutlet var secondInputField: UITextField!
var Secondtext:String!
var delegate:secondViewDelegate!
override func viewDidLoad() {
super.viewDidLoad()
secondOutputField.text = Secondtext
}
//popping back to first view controller using data
#IBAction func popBtn(_ sender: Any) {
if delegate?.popdata != nil
{
delegate.popdata(value: secondInputField.text!)
}
navigationController?.popViewController(animated: true)
}
//pushing to third view controller
#IBAction func thirdPageBtn(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController
navigationController?.pushViewController(vcThird, animated: true)
}
}
ThirdViewController.swift
import UIKit
protocol thirdviewDelegate //delegate to pass data from thirs view to first view
{
func thirdView(datas:String)
}
class ThirdViewController: UIViewController {
var thirddelegate:thirdviewDelegate!
#IBOutlet var thirdTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad
}
#IBAction func thirdViewInvoke(_ sender: Any) {
if thirddelegate?.thirdView != nil
{
thirddelegate.thirdView(datas: thirdTextField.text!)
}
}
}
OutPut Screenshot :
Only passing data from third to first view controller is not working help me to solve this issue...Thanking you
You can use NotificationCenter to receive data.
On press of your Get Data button in your ThirdViewController, post your notification.
let myDataToPass : [String: Any] = ["myData" : yourStringValue]
NotificationCenter.default.post(name: Notification.Name("getDataPressed"), object: myDataToPass)
In your FirstViewController, add observer in viewDidLoad that will listen for the notification:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(receiveInitialDetails(notification:)), name: Notification.Name("getDataPressed"), object: nil)
}
//once the notification is received, this function will be called and you can get the data that you passed in thirdVC using notification.userInfo
#objc func receiveInitialDetails(notification : Notification) {
let data = notification.userInfo as! [String:Any]
let yourStringValue = data["myData"] as! String
// you have your data here. downcast it to your desired data type.
}
you can implement it by using notification center
below I give a small example how I pass imageDataDict in my project
let imageDataDict:[String: UIImage] = ["image": image]
// Post a notification
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil, userInfo: imageDataDict)
// Register to receive notification in your class
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.showSpinningWheel(_:)), name: notificationName, object: nil)
// handle notification
func showSpinningWheel(notification: NSNotification) {
if let image = notification.userInfo?["image"] as? UIImage {
// do something with your image
}
}
You can save the third view controller data to UserDefaults. Get data from User Defaults at first View controller :
`UserDefaults.standard.set(yourData, forKey: "yourDataKey")` // at third view controller
`UserDefaults.standart.string(forKey: "yourDataKey")` //first view controller
Hope it helps...
Reason your code not working:
As soon as you move to second view controller , your third view controller is deallocated from memory. Below line has no meaning in first view controller
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self
You move to second view controller , third view controller is set to nil.
Either you can use NSNotificationCenter to move data from third to first.
If you want to go with protocols, than make below code change.
Code : ViewController.swift
Remove the code:
let vcThird = storyboard.instantiateViewController(withIdentifier: "third") as! ThirdViewController //third view
vcThird.thirddelegate = self
ThirdViewController.swift
protocol thirdviewDelegate //delegate to pass data from thirs view to first view
{
func thirdView(datas:String)
}
class ThirdViewController: UIViewController {
var thirddelegate:thirdviewDelegate?
#IBOutlet var thirdTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func thirdViewInvoke(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstView = storyboard.instantiateViewController(withIdentifier: "first") as! ViewController //third view
self.thirddelegate = firstView
self.thirddelegate?.thirdView(datas: thirdTextField.text ?? "default value")
self.navigationController?.popToRootViewController(animated: true)
}
}
Related
Hai guys I am new to iOS development and still learning
I have a three View controllers ,viewController, SViewController, TViewController
in SviewController I have notification sender.post method on clicking a button
in viewController, TViewController viewdidload() methods I have .addobserver() methods
when I click a button on SViewController and post the notification
View Controller's selector is executed and not the TviewController
I have loaded the TviewController in FirstVC i.e ViewController viewdidload() method only
since segue performs again a viewdidload() from SviewController to TviewController with another button ,I tried to allocate a completion handler string to global variable so that it value remains same and print it(changed value) when view is loaded again, then I came to know that the completion handler is not at all executing here is the code
ViewController
import UIKit
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
#objc func fhandle(notification:Notification){
label.text = "Haii welcome to the hyderabad"
}
}
SViewController
import UIKit
var temp:String = "HHH"
class SViewController: UIViewController {
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
TviewController
import UIKit
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)) , name: .fnote, object: nil)
print(temp)
print("happy")
}
#objc func handler(notification:Notification)
{
print("jackson")
label2.text = "Hurray"
temp = label2.text ?? "Nothing"
}
}
Can Some one please help me with this
You haven't shown or explained where / when / how you're going to display the view from TViewController, but to explain the first thing you're doing wrong...
In this code:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
print("journey")
}
as soon as viewDidLoad() finishes (that is, immediately after the print("journey") line), your tvc instance of TViewController is destroyed. That is, it's dumped from memory and no code inside it can execute, because it no longer exists.
That's called "going out of scope."
So, when you try to post a notification from SViewController, there is no TViewController in existence so its code is not running to "observe" the notification.
Here is a very simple example of multiple view controllers observing a notification:
When you push from ViewController to TViewController, you get a new instance of TViewController and the original instance of ViewController still exists.
When you push from TViewController to SViewController, you get a new instance of SViewController and both ViewController and TViewController still exist.
When you tap the "Post" button, ViewController and TViewController will each execute the func you assigned to observe the notification.
When you then go Back to TViewController, you'll see its label text has changed, and when you go back to ViewController you'll see its label text has also changed.
Note that if you then push to TViewController again, you get a new instance and its label will be back at its original text.
extension Notification.Name{
static var fnote = Notification.Name("fnote")
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(fhandle(notification:)), name: .fnote, object: nil)
print("journey")
}
#objc func fhandle(notification:Notification) {
print("Got it!")
label.text = "Haii welcome to the hyderabad"
}
}
class TViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handler(notification:)), name: .fnote, object: nil)
print("happy")
}
#objc func handler(notification:Notification) {
print("jackson")
label2.text = "Hurray"
}
}
class SViewController: UIViewController {
#IBOutlet var infoLabel: UILabel!
#IBAction func click(_ sender: Any) {
NotificationCenter.default.post(name: .fnote, object: nil)
infoLabel.text = "Notification Posted"
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
let storyboad = UIStoryboard(name: "Main", bundle: nil)
let tvc = storyboad.instantiateViewController(identifier: "1") as! TViewController
let _ = tvc.view
print(tvc.isViewLoaded)
This is not the way to test the notification. Push your "TViewController" controller from the "ViewController" controller. Then post notification from your target controller.
I have 2 UIViewControllers, ViewController, SecondViewController. I defined delegate function in VC, and using in Second VC. But delegate functions not calling in Second VC.
This is mu first VC code
import UIKit
//Step1:
protocol testDelegate {
func testFunction(string1: String, string2:String)
func math(a:Int, b:Int)
}
class ViewController: UIViewController {
//Step2:
var delegateVariable: testDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func moveToSecondVC(_ sender: Any) {
let nav = self.storyboard?.instantiateViewController(withIdentifier: "SVC") as! SecondViewController
//Step3:
delegateVariable?.testFunction(string1: "String1", string2: "String2")
delegateVariable?.math(a:30, b:10)
self.navigationController?.pushViewController(nav, animated: true)
}
}
My second VC code
import UIKit
//Step4:
class SecondViewController: UIViewController , testDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Step5:
let svc = ViewController()
svc.delegateVariable = self
}
#IBAction func btn(_ sender: Any) {
//Step5:
let svc = ViewController()
svc.delegateVariable = self
}
//Step6:
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
print(a+b)
print(a-b)
print(a*b)
}
}
Here i'm just passing small amount of data for practice, but can any one please suggest some high level delegate example tutorial links for me.
This is why nothing is happening...
let svc = ViewController()
svc.delegateVariable = self
You are creating a NEW ViewController, not using the one that is actually in use.
It does not look like you are using the delegate pattern properly. Your ViewController should not be calling code on other view controllers.
SecondViewController should "do stuff" and then let ViewController know what it has done.
For the Math function you could just use a new class (not a view controller) and create and use this as needed. You do not need a ViewController for this.
An example of using a delegate might be something like:
protocol CreateProfileDelegate: class {
func didCreateProfile(profile: Profile?)
func didCancelCreateProfile()
}
class ViewController: UIViewController {
func showCreateProfile() {
let vc = CreateProfileViewController()
vc.delegate = self
present(vc, animated: true)
}
}
extension ViewController: CreateProfileDelegate {
func didCreateProfile(profile: Profile?) {
// show the profile?
}
func didCancelCreateProfile() {
// show an alert maybe?
}
}
This way the SecondViewController (CreateProfileViewController) basically tells the first that something has happened so that it can react to it.
in SecondViewController you are setting....
let svc = ViewController()
svc.delegateVariable = self
That just create an object of ViewController() class and then you set the delegate. So when the obj. of the scope is finished then the memory of the object will be increased automatically.
The flow should like below....
Create an object of the Viewcontroller in SecondViewController and set the delegate
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.delegateVariable = self
Then push the view controller in to the navigation stack.
self.navigationController?.pushViewController(svc, animated: true)
Implement the delegate method of testDelegate in SecondViewController
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
}
EDIT
The final code of the SecondViewController Will be...
import UIKit
class SecondViewController: UIViewController , testDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btn(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ViewController") as! ViewController
vc.delegateVariable = self
self.navigationController?.pushViewController(svc, animated: true)
}
//MARK:- TestDelegate Methods
func testFunction(string1: String, string2: String) {
print(string1+string2)
}
func math(a:Int, b:Int) {
print(a+b)
print(a-b)
print(a*b)
}
}
I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self
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...
}
}
I tried many methods to send data from my popup view controller to main view controller. but failed. can anybody help me with this.
i am using a "present as popover" segue. i want to the text entered in textfield of popover view as the label text of main view.
From Popup View, Data send to Main ViewController using protocol in Swift 3.
enter image description here
Complete Details are given below...
1. View Controller Implementing with Protocol named sendDataToViewProtocol.
import UIKit
class ViewController: UIViewController,sendDataToViewProtocol {
#IBOutlet weak var lshowDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnShowPopUpDialog(_ sender: Any) {
let popUpVc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "PopupVIewController") as! PopupVIewController
//Don't forget initialize protocal deletage
popUpVc.delegate = self
self.addChildViewController(popUpVc)
popUpVc.view.frame = self.view.frame
self.view.addSubview(popUpVc.view)
popUpVc.didMove(toParentViewController: self)
}
func inputData(data: String) {
lshowDataLabel.text = data
}
}
Popup View Controller With Protocol named sendDataToViewProtocol below.
3.protocol declare outside the PopupVIewController.
Don't forget to assign ViewController to PopupVIewController .
In viewController withIdentifier: "PopupVIewController" , "PopupVIewController" is PopupVIewController storyborad Id.
Please see the attached image.
import UIKit
protocol sendDataToViewProtocol {
func inputData(data:String)
}
class PopupVIewController: UIViewController {
//Protocol object
var delegate:sendDataToViewProtocol? = nil
#IBOutlet weak var txtInputFieldText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor
.black.withAlphaComponent(0.8)
}
#IBAction func btnSendDataToViewController(_ sender: Any) {
//"Check Delegate nil"
if(delegate != nil){
//Check textField is empty
if(txtInputFieldText.text != ""){
//set textField Data to protocol Function
delegate?.inputData(data: txtInputFieldText.text!)
self.view.removeFromSuperview()
}
}
}
#IBAction func btnClose(_ sender: Any) {
self.view.removeFromSuperview()
}
}
First of all, keep a temporary variable in your Main ViewController. Let's call it:
var somethingCool: String?
Then, in your popup ViewController code, assuming you have your segue trigger there, you will need to add in a new method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "your_segue_identifier" {
if let vc = segue.destination as? MainViewController {
vc.somethingCool = "whatever_you_want"
}
}
}