I'm applying for a junior developer position and I've got a very specific task, that already took me 3 days to complete. Sounds easy - pass data to rootViewController.
That's what I've done:
1)
private func userDefaultsToRootController() {
let input = textField.text!
defaults.set(input, forKey: "SavedLabel")
navigationController?.popViewController(animated: true)
}
private func segueToRootViewController() {
let destinationVC = MainScreen1()
let input = textField.text!
if input == "" { self.navigationController?.popToRootViewController(animated: true) }
destinationVC.input = input
navigationController?.pushViewController(destinationVC, animated: true)
}
private func popToNavigationController() {
let input = textField.text!
if let rootVC = navigationController?.viewControllers.first as? MainScreen1 {
rootVC.input = input
}
navigationController?.popToRootViewController(animated: true)
}
I've used CoreData
But here is the difficult part - I've got an email, that all these methods are not good enough and I need to use delegate and closure. I've done delegation and closures before, but when I popToRootViewController delegate method passes nil. Could you at least point where to find info about this?
** ADDED **
There are 2 View Controllers: Initial and Second one.
That's what I have in the Initial View Controller:
var secondVC = MainScreen2()
override func viewDidLoad() {
super.viewDidLoad()
secondVC.delegate = self
}
That's how I push SecondViewController
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
navigationController?.pushViewController(nextViewController, animated: true)
}
In SecondViewController I've got this protocol
protocol PassData {
func transferData(text: String)
}
Also a delegate:
var delegate: PassData?
This is how I go back to initial view controller
#objc private func buttonTapped(_ sender: CustomButton) {
if let input = textField.text {
print(input)
self.delegate?.transferData(text: input)
self.navigationController?.popToRootViewController(animated: true)
}
}
Back to the Initial view controller where I've implemented delegate method
extension MainScreen1: PassData {
func transferData(text: String) {
print("delegate called")
label.text = text
}
}
Delegate doesn't get called.
BASED ON YOUR EDIT:
You must set the delegate in buttonTapped
#objc private func buttonTapped(_ sender: CustomButton) {
let nextViewController = MainScreen2()
nextViewController.delegate = self // HERE WHERE YOU SET THE DELEGATE
navigationController?.pushViewController(nextViewController, animated: true)
}
You can delete the second instance and your code in viewDidLoad. That's not the instance you push.
This should point you in the right direction to use delegation and completion handler.
protocol YourDelegateName {
func passData(data:YourDataType)
}
class SecondViewController: UIViewController {
var delegate: YourDelegateName?
func passDataFromSecondViewController(){
YourCoreDataClass.shared.getCoreData { (yourStringsArray) in
self.delegate?.passData(data: yourStringsArray)
self.navigationController?.popToRootViewController(animated: true)
}
}
class InitialViewController: UIViewController, YourDelegateName {
override func viewDidLoad() {
super.viewDidLoad()
// or whenever you instantiate your SecondViewController
let secondViewController = SecondViewController()
secondViewController.delegate = self //VERY IMPORTANT, MANY MISS THIS
self.navigationController?.pushViewController(createVC, animated: true)
}
func passData(data:YourDataType){
//user your data
}
}
class YourCoreDataClass: NSObject {
static let shared = YourCoreDataClass()
func getCoreData (completion: ([String]) -> ()){
........... your code
let yourStringsArray = [String]() // let's use as example an array of strings
//when you got the data your want to pass
completion(yourStringsArray)
}
}
Related
I've followed the instructions here but I'm still unsure about this part:
modalVC.delegate=self;
self.presentViewController(modalVC, animated: true, completion: nil)
I've tried instantiating the view controller programmatically but still to no avail.
here's my code for when dismissing the modal view controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true) {
//
}
}
I'm using storyboard to segue with modal view.
This is the data I wish to transfer back to the parent view controller:
var typeState = "top"
var categoryState = "casual"
Which are two String values.
I've tried to pass data from the modal view controller as shown:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendValue(value: "success")
if let presenter = presentingViewController as? OOTDListViewController {
presenter.receivedValue = "test"
}
}
whereas on the parent view controller I did as such:
func sendValue(value: NSString) {
receivedValue = value as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(receivedValue)
}
I still couldnt receive any value when I hit the print button.
Modal view controller:
protocol ModalViewControllerDelegate
{
func sendData(typeState: String, categoryState: String)
}
var delegate:ModalViewControllerDelegate!
var typeState = "top"
var categoryState = "casual"
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendData(typeState: typeState as String, categoryState: categoryState as String)
}
Parent view controller:
class parentViewController: UICollectionViewController, ModalViewControllerDelegate {
var typeState: String?
var categoryState: String?
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState as String
self.categoryState = categoryState as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(typeState)
}
Here's my new code without using delegate method:
Modal view Controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
if let presenter = presentingViewController as? OOTDListViewController {
presenter.typeState = typeState
presenter.categoryState = categoryState
}
}
OOTDListViewController:
#IBAction func presentModalView(_ sender: UIBarButtonItem) {
let modalView = storyboard?.instantiateViewController(withIdentifier: "filterViewController") as! ModalViewController
let navModalView: UINavigationController = UINavigationController(rootViewController: modalView)
self.present(navModalView, animated: true, completion: nil)
}
#IBAction func printValue(_ sender: UIButton) {
print(typeState)
print(categoryState)
}
Depending on the data you want to pass, you can create a property in the presenting view controller, which you can set when dismissing the modal view controller, so you can spare yourself the delegate.
For example, you have a ContactsViewController, holding a var contacts: [Contact] = [] property. When you want to create a new contact, you present a modal view controller with the different values you need to create a new Contact object. When you are done and want to dismiss the view controller, you call the function as you did in your code, but set the property in the ContactsViewController. It will look something like this:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let presenter = presentingViewController as? ContactsViewController {
presenter.contacts.append(newContact)
}
dismiss(animated: true, completion: nil)
}
If you don't want to use a delegate, this is how you go about it:
In your OOTDListViewController :
var testValue: String = ""
#IBAction func printReceivedValue(_ sender: UIButton) {
print(testValue)
}
In your modal view controller (I'll call it PresentedViewController) :
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
// if your OOTDListViewController is part of a UINavigationController stack, this check will probably fail.
// you need to put a breakpoint here and check if the presentingViewController is actually a UINavigationController.
// in that case, you will need to access the viewControllers variable and find your OOTDListViewController
if let presenter = presentingViewController as? OOTDListViewController {
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
If you want to use a delegate, this is how to do it:
In your OOTDListViewController:
protocol ModalDelegate {
func changeValue(value: String)
}
class OOTDListViewController: ModalDelegate {
var testValue: String = ""
#IBAction func presentViewController() {
// here, you either create a new instance of the ViewController by initializing it, or you instantiate it using a storyboard.
// for simplicity, I'll use the first way
// in any case, you cannot use a storyboard segue directly, bevause you need access to the reference of the presentedViewController object
let presentedVC = PresentedViewController()
presentedVC.delegate = self
present(presentedVC, animated: true, completion: nil)
}
func changeValue(value: String) {
testValue = value
print(testValue)
}
}
In your PresentedViewController:
class PresentedViewController {
var delegate: ModalDelegate?
var testValue: String = ""
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let delegate = self.delegate {
delegate.changeValue(testValue)
}
dismiss(animated: true, completion: nil)
}
}
If using a navigation controller you will have to first grab the UINavigation Controller and then get the correct ViewController from the Navigation Controller stack.
Here's how my code looked in that case.
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! OOTDListViewController
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
I am using the a tab bar so the working code is as below:
if let tabBar = self.presentingViewController as? UITabBarController {
let homeNavigationViewController = tabBar.viewControllers![0] as? UINavigationController
let homeViewController = homeNavigationViewController?.topViewController as! HomeController
homeViewController._transferedLocationID = self.editingLocationID!
}
You need to call the delegate method in dismissViewController method
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
delegate?.sendData(typeState: "top", categoryState: "casual")
self.dismiss(animated: true) {
//
}
}
in you Modal ViewController class create delegate
weak var delegate: MyProtocol?
create a protocol with the method name sendData in MyProtocol and in your presentingViewController where you are assigning the delegate, implement the MyProtocol method
protocol MyProtocol: AnyObject {
func sendData(typeState: String, categoryState: String)
}
class ViewController: UIViewController, MyProtocol {
var typeState: String?
var categoryState: String?
override func viewDidApear() {
super.viewDidApear()
presentNewModalVC()
}
func presentNewModalVC() {
let modalVC = NewModalViewControllerToBePresented()
modalVC.delegate = self
present(modalVC, animated: true)
}
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState
self.categoryState = categoryState
}
}
Im having button in all viewcontrollers to change language
LanguageViewController.swift
class LanguageViewController: UIViewController {
#IBAction func actionChange(_ sender: Any) {
L102Language.currentAppleLanguage()
L102Language.setAppleLAnguageTo(lang: "en")
// below code to refresh storyboard
self.viewDidLoad()
}
}
L102Language.swift
class func currentAppleLanguage() -> String{
let userdef = UserDefaults.standard
let langArray = userdef.object(forKey: APPLE_LANGUAGE_KEY) as! NSArray
let current = langArray.firstObject as! String
let endIndex = current.startIndex
let currentWithoutLocale = current.substring(to: current.index(endIndex, offsetBy: 2))
return currentWithoutLocale
}
/// set #lang to be the first in Applelanguages list
class func setAppleLAnguageTo(lang: String) {
let userdef = UserDefaults.standard
userdef.set([lang,currentAppleLanguage()], forKey: APPLE_LANGUAGE_KEY)
userdef.synchronize()
}
I inherited LanguageViewController in all my FirstViewCOntroller, SecondController as below
class FirstViewController: LanguageViewController {
}
class SecondController: LanguageViewController {
}
If I call self.viewDidLoad() it fails to change language from view defined in storyboard. How to reload storyboard, so that the language should change in all viewcontroller,if any button from any viewcontroller is clicked? Thanks!
You can use NotificationCenter for reloading the view controllers content, this will also reload the content of view controllers that are not visible.
extension Notification.Name {
static let didChangeLanguage = Notification.Name("didChangeLanguage")
}
override func viewDidLoad() {
//Add a listener
NotificationCenter.default.addObserver(self, selector: #selector(onDidChangeLanguage(_:)), name: .didChangeLanguage, object: nil)
}
#IBAction func actionChange(_ sender: Any) {
L102Language.currentAppleLanguage()
L102Language.setAppleLAnguageTo(lang: "en")
// Notify about the change.
NotificationCenter.default.post(name: .didChangeLanguage, object: self, userInfo: nil)
}
#objc func onDidChangeLanguage(_ notification:Notification) {
// reload content using selected language.
}
Correct me if I'm wrong. but I think you don't need to reload all view controllers. you just need to update them when they get displayed, view controllers are behind the presented one are not visible for the user.
for doing that you can do something like this:
var currentLanguage = ""
override func viewDidLoad() {
currentLanguage = currentAppleLanguage()
loadContentForLanguage(currentLanguage)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// this will be executed every time this sceen gets display
if currentLanguage != currentAppleLanguage() {
currentLanguage = currentAppleLanguage()
loadContentForLanguage(currentLanguage)
}
}
func loadContentForLanguage(_ currentLanguage: String) {
//here it goes whatever you currently have in viewDidLoad
}
My apologies if this does not compile, my swift is really rusty.
I've followed the instructions here but I'm still unsure about this part:
modalVC.delegate=self;
self.presentViewController(modalVC, animated: true, completion: nil)
I've tried instantiating the view controller programmatically but still to no avail.
here's my code for when dismissing the modal view controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true) {
//
}
}
I'm using storyboard to segue with modal view.
This is the data I wish to transfer back to the parent view controller:
var typeState = "top"
var categoryState = "casual"
Which are two String values.
I've tried to pass data from the modal view controller as shown:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendValue(value: "success")
if let presenter = presentingViewController as? OOTDListViewController {
presenter.receivedValue = "test"
}
}
whereas on the parent view controller I did as such:
func sendValue(value: NSString) {
receivedValue = value as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(receivedValue)
}
I still couldnt receive any value when I hit the print button.
Modal view controller:
protocol ModalViewControllerDelegate
{
func sendData(typeState: String, categoryState: String)
}
var delegate:ModalViewControllerDelegate!
var typeState = "top"
var categoryState = "casual"
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
delegate?.sendData(typeState: typeState as String, categoryState: categoryState as String)
}
Parent view controller:
class parentViewController: UICollectionViewController, ModalViewControllerDelegate {
var typeState: String?
var categoryState: String?
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState as String
self.categoryState = categoryState as String
}
#IBAction func printReceivedValue(_ sender: UIButton) {
print(typeState)
}
Here's my new code without using delegate method:
Modal view Controller:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
self.dismiss(animated: true, completion: nil)
if let presenter = presentingViewController as? OOTDListViewController {
presenter.typeState = typeState
presenter.categoryState = categoryState
}
}
OOTDListViewController:
#IBAction func presentModalView(_ sender: UIBarButtonItem) {
let modalView = storyboard?.instantiateViewController(withIdentifier: "filterViewController") as! ModalViewController
let navModalView: UINavigationController = UINavigationController(rootViewController: modalView)
self.present(navModalView, animated: true, completion: nil)
}
#IBAction func printValue(_ sender: UIButton) {
print(typeState)
print(categoryState)
}
Depending on the data you want to pass, you can create a property in the presenting view controller, which you can set when dismissing the modal view controller, so you can spare yourself the delegate.
For example, you have a ContactsViewController, holding a var contacts: [Contact] = [] property. When you want to create a new contact, you present a modal view controller with the different values you need to create a new Contact object. When you are done and want to dismiss the view controller, you call the function as you did in your code, but set the property in the ContactsViewController. It will look something like this:
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let presenter = presentingViewController as? ContactsViewController {
presenter.contacts.append(newContact)
}
dismiss(animated: true, completion: nil)
}
If you don't want to use a delegate, this is how you go about it:
In your OOTDListViewController :
var testValue: String = ""
#IBAction func printReceivedValue(_ sender: UIButton) {
print(testValue)
}
In your modal view controller (I'll call it PresentedViewController) :
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
// if your OOTDListViewController is part of a UINavigationController stack, this check will probably fail.
// you need to put a breakpoint here and check if the presentingViewController is actually a UINavigationController.
// in that case, you will need to access the viewControllers variable and find your OOTDListViewController
if let presenter = presentingViewController as? OOTDListViewController {
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
If you want to use a delegate, this is how to do it:
In your OOTDListViewController:
protocol ModalDelegate {
func changeValue(value: String)
}
class OOTDListViewController: ModalDelegate {
var testValue: String = ""
#IBAction func presentViewController() {
// here, you either create a new instance of the ViewController by initializing it, or you instantiate it using a storyboard.
// for simplicity, I'll use the first way
// in any case, you cannot use a storyboard segue directly, bevause you need access to the reference of the presentedViewController object
let presentedVC = PresentedViewController()
presentedVC.delegate = self
present(presentedVC, animated: true, completion: nil)
}
func changeValue(value: String) {
testValue = value
print(testValue)
}
}
In your PresentedViewController:
class PresentedViewController {
var delegate: ModalDelegate?
var testValue: String = ""
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let delegate = self.delegate {
delegate.changeValue(testValue)
}
dismiss(animated: true, completion: nil)
}
}
If using a navigation controller you will have to first grab the UINavigation Controller and then get the correct ViewController from the Navigation Controller stack.
Here's how my code looked in that case.
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! OOTDListViewController
presenter.testValue = "Test"
}
dismiss(animated: true, completion: nil)
}
I am using the a tab bar so the working code is as below:
if let tabBar = self.presentingViewController as? UITabBarController {
let homeNavigationViewController = tabBar.viewControllers![0] as? UINavigationController
let homeViewController = homeNavigationViewController?.topViewController as! HomeController
homeViewController._transferedLocationID = self.editingLocationID!
}
You need to call the delegate method in dismissViewController method
#IBAction func dismissViewController(_ sender: UIBarButtonItem) {
delegate?.sendData(typeState: "top", categoryState: "casual")
self.dismiss(animated: true) {
//
}
}
in you Modal ViewController class create delegate
weak var delegate: MyProtocol?
create a protocol with the method name sendData in MyProtocol and in your presentingViewController where you are assigning the delegate, implement the MyProtocol method
protocol MyProtocol: AnyObject {
func sendData(typeState: String, categoryState: String)
}
class ViewController: UIViewController, MyProtocol {
var typeState: String?
var categoryState: String?
override func viewDidApear() {
super.viewDidApear()
presentNewModalVC()
}
func presentNewModalVC() {
let modalVC = NewModalViewControllerToBePresented()
modalVC.delegate = self
present(modalVC, animated: true)
}
func sendData(typeState: String, categoryState: String) {
self.typeState = typeState
self.categoryState = categoryState
}
}
I am having issues trying to pass the data back to the ViewController (from BarCodeScannerViewController to TableViewController)
SecondVC (BarCodeScannerViewController.swift):
#objc func SendDataBack(_ button:UIBarButtonItem!) {
if let presenter = self.presentingViewController as? TableViewController {
presenter.BarCode = "Test"
}
self.dismiss(animated: true, completion: nil)
}
FirstVC (TableViewController.swift):
// The result is (BarCode - )
var BarCode: String = ""
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("BarCode - \(BarCode)")
}
Each time ViewWillAppear is running the value is not set, what could be causing this issue?
You should use the delegate pattern. I doubt in your code above that self.presentingViewController is actually set.
An example of using the delegate pattern for this:
// BarCodeScannerViewController.swift
protocol BarcodeScanningDelegate {
func didScan(barcode: String)
}
class BarCodeScannerViewController: UIViewController {
delegate: BarcodeScanningDelegate?
#objc func SendDataBack(_ button:UIBarButtonItem!) {
delegate?.didScan(barcode: "Test")
}
}
// TableViewController
#IBAction func scanBarcode() {
let vc = BarCodeScannerViewController()
vc.delegate = self
self.present(vc, animated: true)
}
extension TableViewController: BarcodeScanningDelegate {
func didScan(barcode: String) {
print("[DEBUG] - Barcode scanned: \(barcode)")
}
}
I have two View Controllers: "DiscoverViewController" and "LocationRequestModalViewController".
The first time a user opens the "DiscoverViewController", I overlay "LocationRequestModalViewController" which contains a little blurb about accessing the users location data and how it can help them.
On the "LocationRequestModalViewController" there are two buttons: "No thanks" and "Use location". I need to send the response from the user back to the "DiscoverViewController"
I have done some research and found that delegates/protocols are the best way to do it, so I followed a guide to get that working, but I'm left with 2 errors and can't figure them out.
The errors are:
On DiscoverViewController
'DiscoverViewController' is not convertible to 'LocationRequestModalViewController'
On LocationRequestModalViewController
'LocationRequestModalViewController' does not have a member name 'sendBackUserLocationDataChoice'
I've marked where the errors are happen in the following files:
DiscoverViewController.swift
class DiscoverViewController: UIViewController, UITextFieldDelegate, CLLocationManagerDelegate, LocationRequestModalViewControllerDelegate {
func showLocationRequestModal() {
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var locationRequestVC: AnyObject! = storyboard.instantiateViewControllerWithIdentifier("locationRequestVC")
self.presentingViewController?.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.tabBarController?.presentViewController(locationRequestVC as UIViewController, animated: true, completion: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let vc = segue.destinationViewController as LocationRequestModalViewController
vc.delegate = self //This is where error 1 happens
}
func sendBackUserLocationDataChoice(controller: LocationRequestModalViewController, useData: Bool) {
var enableData = useData
controller.navigationController?.popViewControllerAnimated(true)
}
override func viewDidLoad() {
super.viewDidLoad()
showLocationRequestModal()
}
}
LocationRequestModalViewController
protocol LocationRequestModalViewControllerDelegate {
func sendBackUserLocationDataChoice(controller:LocationRequestModalViewController,useData:Bool)
}
class LocationRequestModalViewController: UIViewController {
var delegate:LocationRequestModalViewController? = nil
#IBAction func dontUseLocationData(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func useLocationData(sender: AnyObject) {
delegate?.sendBackUserLocationDataChoice(self, useData: true) // This is where error #2 happens
}
override func viewDidLoad() {
super.viewDidLoad()
//Modal appearance stuff here...
}
}
The answer is in your question itself. Both errors tells the exact reason.
Issue 1
let vc = segue.destinationViewController as LocationRequestModalViewController
vc.delegate = self //This is where error 1 happens
The self is of type DiscoverViewController
But you declared the delegate as:
var delegate:LocationRequestModalViewController? = nil
You need to change that to:
var delegate:DiscoverViewController? = nil
Issue 2
The same reason, LocationRequestModalViewController does not confirm to the LocationRequestModalViewControllerDelegate, change the delegate declaration.
You have defined your delegate as having type LocationRequestModalViewController which does not conform to LocationRequestModalViewControllerDelegate.