Swift / how to call delegate with popViewController - ios

I have read this thread (and similar others) from bottom to top, but it doesn't fit my needs at all.
I have a UIViewController inside UIPageViewController within a UINavigationController. Navigating to a 2nd ViewController. Navigating to a 3rd ViewController and want to pop back to 2nd ViewController delivering data.
My code currently:
protocol PassClubDelegate {
func passClub(passedClub: Club)
}
class My3rdVC: UIViewController {
var clubs: [Club] = []
var passClubDelegate: PassClubDelegate?
....
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let club = clubs[indexPath.row]
self.passClubDelegate?.passClub(club)
navigationController?.popViewControllerAnimated(true)
}
My 2nd VC:
class My2ndVC: UIViewController, PassClubDelegate {
var club = Club()
func passClub(passedClub: Club) {
SpeedLog.print("passClub called \(passedClub)")
club = passedClub
}
passClub is not called. I'm sure it's because I didn't set the delegate to the My2ndVC, but how would I do that? All the solutions I have found wanting me to use a) segue or b) instantiate a My2ndVC new, what doesn't make any sense since it's still in memory and I want to pop back to go back in hierarchy. What am I missing? What are my possibilities? Help is very appreciated.
PS: I'm not using any segues. My3rdVC is called by:
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
self.navigationController?.pushViewController(vc, animated: true)

You can set the delegate of My3rdVC in the prepareForSegue method of My2ndVC.
class My2ndVC: UIViewController, PassClubDelegate {
...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
switch segue.destinationController {
case let controller as My3rdVC:
controller.passClubDelegate = self
}
}
}
This is assuming you have created a segue in your storyboard that pushes My3rdVC from My2ndVC onto the navigation controller stack, which I'm assuming you have. So just try simply pasting this prepareForSegue method into My2ndVC and see if it works.
UPDATE
let vc = stb.instantiateViewControllerWithIdentifier("My3rdVC") as! My3rdVC
vc.passClubDelegate = self
navigationController?.pushViewController(vc, animated: true)

When pop one VC to another you can pass data using protocol by declare delegate variable as static. Here in the following example we pop SecondVC to FirstVC and we pass a string.
class FirstVC: UIViewController,getDataDelegateProtocol {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
SecondVC.delegate = self
}
func getData(tempStr: String) {
label.text = tempStr
}
#IBAction func buttonClick(_ sender: UIButton){
let nav = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondVC") as! SecondVC
self.navigationController?.pushViewController(nav, animated: true)
}
}
Another View Controller
protocol getDataDelegateProtocol{
func getData(tempStr: String)
}
class SecondVC: UIViewController {
#IBOutlet weak var label: UILabel!
static var delegate: getDataDelegateProtocol?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonClick(_ sender: UIButton){
SecondVC.delegate?.getData(tempStr: "Received data from SecondVC")
self.navigationController?.popViewController(animated: true)
}
}

Related

how to update label of other viewController in swift

I'm trying to set UIbutton's text in firstVC as selectedCity variable when it selected in tableViewCell in SecondVC . I cannot use segue or tabBarController. Updated photo of ui
FirstVC
import UIKit
class homeView: UIViewController{
#IBOutlet weak var selectRegion: UIButton!
}
SecondVC
import UIKit
class selectCityPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var selectedCity: String!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0{
selectedCity = "Almaty"
}
if indexPath.row == 1{
selectedCity = "Усть-Каменогорск"
}
dismiss(animated: true, completion: nil)
// when this view controller is dismissed, uibutton's text in next viewcontroller should equal to selectedCity
}
}
You can use delegation. These are the required steps:
Create a delegate protocol that defines the messages sent to the delegate.
Create a delegate property in the delegating class to keep track of the delegate.
Adopt and implement the delegate protocol in the delegate class.
Call the delegate from the delegating object.
SecondVC
import UIKit
//1. Create a delegate protocol
protocol CitySelectionDelegate {
func pickCity(with selectedCity:String)
}
class SelectCityPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var selectedCity: String!
//2. Create a delegate property in the delegating class
var delegate:CitySelectionDelegate?
//other stuff
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
if indexPath.row == 0{
selectedCity = "Almaty"
}
if indexPath.row == 1{
selectedCity = "Усть-Каменогорск"
}
4. Call the delegate from the delegating object.
delegate?.pickCity(with: selectedCity) //call your delegate method
//dismiss(animated: true, completion: nil) when you dismiss is up to you
}
}
HomeViewController
UPDATE: Since you have another VC embedded inside a UINavigationController, both the Home and Select Region Page MUST conform to the delegate and you have to set the delegate inside prepareForSegue method.
// 3. Adopt and implement the delegate protocol
class HomeViewController: UIViewController, CitySelectionDelegate{
#IBOutlet weak var selectRegion: UIButton!
func pickCity(with selectedCity: String) {
self.selectRegion.text = selectedCity
}
/*please pay attention. In this case you must reference the
navigation controller first and the your can get the right
instance of the delegate variable inside your firstVC (I called
firstVC but in your case is Select Region Page*/
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// you MUST set the right identifier
if segue.identifier == "showFirst" {
if let navController = segue.destination as? UINavigationController {
if let firstVC = navController.topViewController as? FirstViewController{
firstVC.delegate = self
}
}
}
}
}
since you have another VC (embedded in a navigation controller), also this one must conform to the delegate like so:
class FirstViewController: UIViewController, CitySelectionDelegate {
var delegate: CitySelectionDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
func pickCity(with selectedCity: String){
// here you simply call the delegate method again and you dismiss the navigation controller
self.delegate?.pickCity(with: selectedCity)
self.navigationController?.dismiss(animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecond" {
if let controller = segue.destination as? SelectCityPage {
controller.delegate = self
}
}
}
you can use delegate or block or notification when secVC action to change the firstVC value you needed.
Or you set a property in secVC and pass the value in firstVC you want to change, So you can change value passed from firstVC in secVC.

Problem performing segue from tableview to viewcontroller

I am trying to perform a segue from a UITableView with news. If you push one of the news, it performs a segue to the specific news you selected.
It is easy and I have done it a few times... but I don't know what am I doing wrong this time.
The NewsDetailViewController is like this:
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsTitle: UILabel!
#IBOutlet weak var newsDate: UILabel!
#IBOutlet weak var newsText: UILabel!
var newsLink: String?
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func closeNews(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
}
And the segue in the NewsTableViewController is like this:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you selected the row: \(indexPath.row)")
tableView.deselectRow(at: indexPath, animated: true)
self.performSegue(withIdentifier: "goToNewsDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.newsTitle.text = "whatevertitle"
}
}
}
And the line: destination.newsLink = "whateverlink"
Works perfectly.
But the line: destination.newsTitle.text = "whatevertitle"
Throws a
fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value.
And I have no idea of what if going on. The same problem happens when trying to initialise the rest of the labels in the destination.
This line is the problem
destination.newsTitle.text = "whatevertitle"
don't access outlets of the destination vc as they not yet loaded send a string and assign it to the label in the destination vc
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToNewsDetail" {
if let destination = segue.destination as? NewsDetailViewController {
destination.newsLink = "whateverlink"
destination.toSend = "whatevertitle"
}
}
}
class NewsDetailViewController: UIViewController {
#IBOutlet weak var newsTitle:UILabel!
var toSend = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.newsTitle.text = toSend
}
}
In the prepareForSegue method, the newsTitle label is still not initialised, so it is nil.
Generally, you shouldn't set the target VC's view's properties in prepareForSegue. You should declare a newsTitleText property in NewsDetailViewController:
var newsTitleText: String!
And set this property instead:
destination.newsTitleText = "whatevertitle"
Then set the newsTitle.text in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
// Hides the navigation bar.
self.navigationController?.setNavigationBarHidden(true, animated: false)
newsTitle.text = newsTitleText
}
When the prepare(for:sender:) method is called, NewsDetailViewController has not loaded the view yet so you can't set the text on a label. What you want to do is create another property on NewsDetailViewController such as var newsTitleText: String?. Then in viewDidLoad you can call newsTitle.text = newsTitleText.

Swift | Dismiss ViewController and Push other ViewController

Solved it: I forgot to declare tvx.selectionDelegate = self in VC1, thats why it never executed
I'v searched very long and fund two similar answers to my problem online:
here
But all of them work with dismissing and then presenting another view, and I want to dismiss and PUSH another ViewC.
I have 3 ViewController:
VC1 lets Call it: DownloadsViewController
VC2 lets Call it: SelectActionController
VC3 lets Call it: fileInfoView
VC1 presents VC2 modally then VC2 should dismiss and VC1 should PUSH VC3 immediately.
I've tried:
VC2:
self.present(vx, animated: true, completion: nil)
and the to put the PUSH animation in completion {} but it crashes.
The next thing I've tried is to make a delegate and if I press a button on VC2 it call VC1 to dismiss VC2 and Push VC3. Like Here:
VC2:
protocol PushViewControllerDelegate: class {
func pushViewController(_ controller: UIViewController)
}
class SelectActionController: UIViewController, UITableViewDelegate, UITableViewDataSource {
weak var delegate: PushViewControllerDelegate?
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if self.delegate != nil {
self.delegate?.pushViewController(self)}
VC1:
func pushViewController(_ controller: UIViewController) {
controller.dismiss(animated: true) { () -> Void in
self.performSegue(withIdentifier: self.segue2, sender: nil)
//let vx = self.storyboard?.instantiateViewController(withIdentifier: "FileInfo") as! fileInfoView
//self.navigationController?.pushViewController(vx, animated: false)
}
}
The Trird thing I tried was to make a global Variable and if it dismisses, just set a Bool variable to true and an func in VC1 should recognize it(my intention). Only problem it doesn't regognize dismissing as ViewDidAppear or stuff like that.
So none of that worked.
Does anyone has an Idea to this?
EDITED after OP comment
Just use a protocol.
Let your first controller adopt it, and add the required function.
Set a protocol variable in the second controller who takes the first one for reference and call the function when you dismiss the second controller.
You can now use the dismiss function to do whatever you want, pass data, send to an other controller...
first view controller :
protocol CustomProtocol {
func dismissed()
}
class AViewController: UIViewController, CustomProtocol {
#IBOutlet weak var button: UIButton!
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 toVC2(_ sender: UIButton) {
let VC = self.storyboard!.instantiateViewController(withIdentifier: "BViewController") as! BViewController
VC.customProtocol = self
VC.view.backgroundColor = .white
self.present(VC, animated: true, completion: nil)
}
func dismissed() {
let yourViewController = UIViewController()
yourViewController.view.backgroundColor = .red
guard let navigationController = self.navigationController else {return}
navigationController.pushViewController(yourViewController, animated: true)
}
}
second view controller :
class BViewController: UIViewController {
var customProtocol: CustomProtocol?
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 quit(_ sender: UIButton) {
self.dismiss(animated: true) {
guard let proto = self.customProtocol else {return}
proto.dismissed()
}
}
}
According to your File from Github, I think you forgot to declare
tvx.selectionDelegate = self
in the
func imagePressed
I hope you need to perform the dismiss action on your
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
in "SelectActionController" right ?
In such case you have to do as follows,
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if self.delegate != nil {
dismiss(animated: true) {
self.delegate?.pushViewController(self)}
}
}
I have shared an example project related to your situation, please refer it.
https://github.com/Bharath-MallowTech/StackoverflowSolutions/tree/master/viewcontrollerDismissAndPush
Could you post some code on what you've been trying? What is self here? Is it VC1, VC2 or VC3?
What you could do is have a look at protocols, so you use VC1 as a delegate and when you dismiss VC2 it will also call it's delegate showVC3(). You then place that delegate.showVC3() in the completion block for your dismiss.
// Declare it in VC1
#protocol MyDelegate {
func showVC3()
}
class VC1: UIViewController, MyDelegate {
var vc2: VC2ViewController = VC2ViewController()
var vc3: VC3ViewController = VC3ViewController()
func showVC2() {
vc2.delegate = self
self.present(self.vc2)
}
func showVC3() {
self.present(self.vc3)
}
}
// In VC2
class VC2: UIViewController {
var delegate: MyDelegate?
func closeVC2() {
self.dismiss(animated: true, completion: {
self.delegate?.showVC3()
})
}
}
Not sure if this works, haven't tried it out since I'm on my PC at the moment. But change it according to your needs and test it out.
Even i had the same situation , that after presenting a view controller , i need to push to some other view controller. If we have vc1, vc2, vc3, We presented vc2 from vc1, then on button click of vc2 we need to push to vc3, what i did is, i set vc3 as root view controller. You can check code as per below.
func goToHomeScreenPage(transition : Bool)
{
let _navigation : UINavigationController!
let startVC : TabbarViewController = Utilities.viewController(name: "TabbarViewController", onStoryboard: StoryboardName.Login.rawValue) as! TabbarViewController
_navigation = UINavigationController(rootViewController: startVC)
_navigation.setNavigationBarHidden(true, animated: true)
let transitionOption = transition ? UIViewAnimationOptions.transitionFlipFromLeft : UIViewAnimationOptions.transitionFlipFromLeft
gotoViewController(viewController: _navigation, transition: transitionOption)
}
the above method gotoviewcontroller() is below,
func gotoViewController(viewController: UIViewController, transition: UIViewAnimationOptions)
{
if transition != UIViewAnimationOptions.transitionCurlUp
{
UIView.transition(with: self.window!, duration: 0.5, options: transition, animations: { () -> Void in
self.window!.rootViewController = viewController
}, completion: { (finished: Bool) -> Void in
// do nothing
})
} else {
window!.rootViewController = viewController
}
}
Declare var window: UIWindow? on top, you can write these methods in appdelegate and call them by creating appdelegate instance.

Passing image between two VC using Modal Segue

I'm trying to pass data, using delegate between two VC, but I can't get why it's not working.
My first VC
class ViewController: UIViewController {
#IBOutlet weak var profileImage: UIImageView!
func downloadPhoto() {
self.performSegue(withIdentifier: "showPhotoFromInternet", sender: nil)
}
}
extension ViewController: IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage) {
profileImage.image = imageToShow
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PresentPhotoFromInternetViewController {
destination.delegate = self
}
}
My second VC
class PresentPhotoFromInternetViewController: UIViewController {
var imageToSend: UIImage?
var delegate: IPresentaionPhotoFormInternetDelegate?
#IBOutlet weak var photoCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
photoCollectionView.allowsMultipleSelection = false
}
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
performSegue(withIdentifier: "sendPhoto", sender: nil)
}
extension PresentPhotoFromInternetViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! photoCollectionViewCell
print("Cell is selected")
iamgeToSend = cell.photoImageView.image
cell.selectedView.backgroundColor = UIColor.green
}
protocol IPresentaionPhotoFormInternetDelegate {
func setNewImage(imageToShow: UIImage)
}
I use present modaly segue for from the first VC to the second, and show form the second to the first
When I perform segue from the second VC there is no updates in my first one, although it passes all breakpoints.
The problem is instead of popping controller you are loading completely new controller in your button action
#IBAction func sendPhotoToPreviousController(_ sender: Any) {
delegate?.setNewImage(imageToShow: iamgeToSend!)
//Comment or remove the performSegue
//performSegue(withIdentifier: "sendPhoto", sender: nil)
//Now simply pop this controller
_ = self.navigationController?.popViewController(animated: true)
// If you are presenting this controller then you need to dismiss
self.dismiss(animated: true)
}
Note: If you are segue is kind of Push then use popViewController or is it kind of Modal than you need to use dismiss.

Get value from Modal View in Swift iOS

i'm trying to start writing Swift and i'm trying to get a value from a modal view controller with no luck.
I have two controllers, the ViewController and modalViewController.
In ViewController i have a UITableView and with a press of a button i open the modalViewController.
Then from a UITextField i pass the value.
I have implement a protocol with delegate and func but somewhere i'm missing something or had it wrong.
ViewController.swift
import UIKit
class ViewController: UIViewController,UITableViewDelegate,modalViewControllerDelegate{
#IBOutlet var table: UITableView!
var tableData = ["First Row","Second Row","Third Row"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
table.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(table:UITableView?,numberOfRowsInSection section: Int) -> Int
{
return tableData.count
}
func tableView(table:UITableView?,cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell!
{
let cell:UITableViewCell = UITableViewCell(style:UITableViewCellStyle.Default,reuseIdentifier:"cell")
cell.textLabel?.text = tableData[indexPath.row]
return cell
}
func sendText(text: NSString) {
tableData.append(text)
} }
modalViewController.swift
import UIKit
protocol modalViewControllerDelegate {
func sendText(var text: NSString)
}
class modalViewController: UIViewController{
let delegate: modalViewControllerDelegate?
#IBOutlet var textField: UITextField?
#IBAction func cancelButton(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func saveButton(sender: AnyObject) {
delegate?.sendText(self.textField!.text)
dismissViewControllerAnimated(true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
}
}
I have no errors in the code, the delegate is not working, it's always nil.
Thanks.
You have to assign the delegate in your first view controller.
Also, you have to change let delegate: modalViewControllerDelegate? to a var, or else you can't change it.
Right now your delegate is empty.
It's unclear how you're accessing ModalViewController. If you're using segues:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "modalViewControllerSegue" {
var destination = segue.destinationViewController as CategoryViewController
destination.delegate = self
}
}
Or if you're doing it programmatically:
var modalViewController = ModalViewController(parameters)
modalViewController.delegate = self
presentViewController(modalViewController, animated: true, completion: nil)
Storyboard identifier:
let destination = UIStoryboard.mainStoryboard().instantiateViewControllerWithIdentifier("ModalViewController") as ModalViewController
delegate = self
showViewController(destination, sender: nil)
EDIT:
If you want to access ModalViewController by selecting a cell you need the tableView: didSelectRowAtIndexPath method:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("modalViewControllerSegue", sender: self)
}
Using this, you'll need the method prepareForSegue to set the delegate.
You have to set your modalViewController's delegate property before presenting it. If you're using segues, you can do this in prepareForSegue(_:).
Also, class names should begin with uppercase letters (modalViewController should be ModalViewController). Only instances should begin with lowercase letters.
Another option, instead of using delegation, is to use an unwind segue. Here's a tutorial: http://www.cocoanetics.com/2014/04/unwinding/
In your case, in your presenting view controller you could have the method:
func returnFromModalView(segue: UIStoryboardSegue) {
// This is called when returning from the modal view controller.
if let modalVC = segue.sourceViewController as? ModalViewController
where segue.identifier == "mySegueID" {
let text = modalVC.textField.text
// Now do stuff with the text.
}
}
And then just link up everything in the Interface Builder as shown in the tutorial.

Resources