Passing data from viewcontroler to tab viewed controllers in swift - ios

I have this login form:
import UIKit
class LoginViewController: UIViewController {
var user : LoginUser?
#IBAction func btnLoginPressed(_ sender: Any) {
if self.textFieldLogin.text?.isEmpty ?? true || self.textFieldPassword.text?.isEmpty ?? true {
self.errorLoginMessage(txt: "Error", title: "Error")
} else {
let cms = ServerConnect()
cms.checkUsersLogin(login: self.textFieldLogin.text, password: self.textFieldPassword.text, completion: { (data) in
switch(data) {
case .succes(let data):
var actionToRun: ()-> Void
let decoder = JSONDecoder()
self.user = try? decoder.decode(LoginUser.self, from: data)
dump(self.user)
// we have an user
if ((self.user?.id ) != nil) {
actionToRun = {
self.performSegue(withIdentifier: "toLoginUser", sender: self)
}
}
// we have an error
else if let json = try? JSONSerialization.jsonObject(with: data, options: []),
let dictionary = json as? [String: Any],
let message = dictionary["komunikat"] as? String,
let title = dictionary["error"] as? String {
// we have an error
actionToRun = {
self.errorLoginMessage(txt: message, title: title)
}
}
// default error
else {
actionToRun = {
self.errorLoginMessage(txt: "Podany login lub hasło jest błędny!!", title: "Błąd")
}
}
DispatchQueue.main.async {
actionToRun()
}
case .error(let error):
print("Error 104: \(error)")
}
})
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "toLoginUser" {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabVC = storyboard.instantiateViewController(withIdentifier: "MainVC") as! UITabBarController
self.present(tabVC, animated: true, completion: {
let vc = tabVC.selectedViewController as! NewsViewController
vc.loggedUser = self.user
})
}
}
func errorLoginMessage(txt: String, title: String){
let alertController = UIAlertController(title: title, message: txt, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: { (action: UIAlertAction!) in
}))
self.present(alertController, animated: true, completion: nil)
}
}
The object is built correctly.
I would like to pass my object from the login view to tab viewed controllers.
I am receiving the object like this:
class NewsViewController: UIViewController {
var loggedUser : LoginUser?
override func viewDidLoad() {
super.viewDidLoad()
print("check object: \(loggedUser)")
dump(loggedUser)
}
#IBAction func testBtnPressed(_ sender: Any) {
print("check object: \(loggedUser)")
dump(loggedUser)
}
}
Unfortunately this code does not work and I always get nil.
My full source code: https://bitbucket.org/trifek/karta-nauka/src/master/
Can I ask for help?

It is not good idea to pass info view controller to view controller which is used by almost all of your view controller's
Like Login information which contains info that is nearly used by all of the screen
Good idea is to create Global Shared class which will store your all global information which is shared among the application.
public final class AppGlobalManager {
static let sharedManager = AppGlobalManager()
var loggedUser : LoginUser?
}
Now whenever user login you can
AppGlobalManager.sharedManager.loggedUser = object
And when user logout
AppGlobalManager.sharedManager.loggedUser = nil
Hope it is helpful

If you are already using segues in the storyboard, you don't have to get the desired view controller from the UIStoryboard, the segues itself provides the destination view controller.
Example:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toLoginUser" {
let tabVC = segue.destination as! UITabBarController
// assuming that `NewsViewController` is the first view controller in the tabbar controller:
let destinationViewController = tabVC.viewControllers?[0] as! NewsViewController
destinationViewController.loggedUser = self.user
}
}
Since you are aiming to pass the LoginUser object to one of the tabbar view controllers, you could get it from the tabbar viewControllers array -as mentioned in the code snippet above-.

Related

Why self delegate is nil?

I want to make a weather application by adding a city name with openweathermap api. But I could not send the city I added in AddCityViewController back to HomeViewController. Because, self?.delegate is nil, in AddCityViewController.swift
#objc private func didTapSaveButton() {
print("clicked save button")
if let city = cityTextfield.text {
let weatherURL = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(city)&APPID=b4251cb51691654da529bccf471596bc&units=imperial")!
let weatherResource = Resource<WeatherViewModel>(url: weatherURL) { data in
let weatherVM = try? JSONDecoder().decode(WeatherViewModel.self, from: data)
return weatherVM
}
Webservice().load(resource: weatherResource) { [weak self] result in
if let weatherVM = result {
if let delegate = self?.delegate {
delegate.addWeatherDidSave(vm: weatherVM)
self?.dismiss(animated: true, completion: nil)
}
}
}
}
}
When I debug the prepare function in HomeViewController.swift was not getting called.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nav = segue.destination as? UINavigationController else {
fatalError("NavigationController not found")
}
guard let addWeatherCityVC = nav.viewControllers.first as? AddCityViewController else {
fatalError("AddWeatherCityController not found")
}
addWeatherCityVC.delegate = self
}
What I want is, I want to pass the city name back to HomeViewController when user press the save button.
extension HomeViewController: AddWeatherDelegate {
func addWeatherDidSave(vm: WeatherViewModel) {
print(vm.name)
}
}
Source code in GitHub
You are not using segue for navigation, so the prepareForSegue method won't get triggered. In your code, you are manually initialising an instance of AddCityViewController and presenting it. So to fix the issue, you have to set delegate to that instance.
#objc private func didTapAddButton() {
let vc = AddCityViewController()
vc.title = "Add City"
vc.delegate = self
let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true)
}
Or else you can use segue for navigation.

Dismiss view controller to previous view controller

I know lots of people may asked this question . i have also find the solution but i am not able to understand what is wrong in my code ?
popcontroller.swift:
protocol DataSendDelegate {
func sendgetattid(pop_att_id: Int, pop_in_time: String)
}
var delegate: DataSendDelegate? = nil
if statuss == "200" {
let send_pop_att_id = att_id
let send_pop_in_time = in_time
self.navigationController?.popViewController(animated: true)
self.dismiss(animated: true) {
self.delegate?.sendgetattid(pop_att_id: send_pop_att_id, pop_in_time: send_pop_in_time)
}
}
In the above code i get the correct value but it does not dismiss and go to next view controller .
attendanceViewController.swift:
class attendanceViewController: UIViewController , DataSendDelegate {
func sendgetattid(pop_att_id: Int, pop_in_time: String) {
DispatchQueue.main.async {
self.inTimeTextField.text = pop_in_time
self.get_att_id = pop_att_id
self.in_time_button.isEnabled = false
self.out_time_button.isEnabled = true
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let popVC = segue.destination as? PopUpViewController
{
popVC.popEmailID = att_emp_id
popVC.delegate = self
}
}
}
Update from your comment:
You didn't present viewController. You have pushed some view controller. So Just pop it. discard the dismiss
self.delegate?.sendgetattid(pop_att_id: send_pop_att_id, pop_in_time: send_pop_in_time)
self.navigationController?.popViewController(animated: true)
If you did self.present(ViewController, animated: true, completion: nil) from the previous ViewController to show the current viewcontroller, you must do
self.dismiss(animated: true) {
self.delegate?.sendgetattid(pop_att_id: send_pop_att_id, pop_in_time: send_pop_in_time)
}
or if you did self.navigationController?.pushViewController(ViewController, animated: true) you must do self.navigationController?.popViewController(animated: true)

Passing data between controller after method is done executed [SWIFT]

I'm trying to send data across another view controller once a button is pressed (I know this question looks repetitive), however, the button being pressed is processing some data. So when the button is clicked, the other view controller is popped up before the needed actual data is sent. I tried both segue calls (prepare for segue and the calling segue) but none seem to work. Here is my code:
#IBAction func login(sender: Any) {
SparkCloud.sharedInstance().login(withUser: email, password: password) { (error:Error?) -> Void in
if let _ = error {
print("Wrong credentials or no internet connectivity, please try again")
}
else {
print("Logged in")
var myPhoton : SparkDevice?
SparkCloud.sharedInstance().getDevices { (devices:[SparkDevice]?, error:Error?) -> Void in
if let _ = error {
print("Check your internet connectivity")
}
else {
if let d = devices {
for device in d {
myPhoton = device
print(myPhoton!)
}
}
}
}
}
}
}
And the segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loggedIn" {
if let destinationVC = segue.destination as? testViewController {
destinationVC.myPhoton = sentDevice
}
}
}
And the other view controller that is receiving the data:
var myPhoton : SparkDevice?
override func viewDidLoad() {
super.viewDidLoad()
print(myPhoton)
}
I receive 'nil', which indicates that when the data has been set, it was before it got set to the data that I wanted from the server. Can someone help me please?
You can try
#IBAction func login(sender: Any) {
SparkCloud.sharedInstance().login(withUser: email, password: password) { (error:Error?) -> Void in
if let _ = error {
print("Wrong credentials or no internet connectivity, please try again")
}
else {
print("Logged in")
var myPhoton : SparkDevice?
SparkCloud.sharedInstance().getDevices { (devices:[SparkDevice]?, error:Error?) -> Void in
if let _ = error {
print("Check your internet connectivity")
}
else {
if let d = devices {
for device in d {
myPhoton = device
print(myPhoton!)
}
self.performSegue(withIdentifier: "loggedIn", sender: myPhoton)
}
}
}
}
}
}
and remove linking the segue directly to the button action in IB
Edit
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loggedIn" {
if let destinationVC = segue.destination as? testViewController {
destinationVC.myPhoton = sender as! SparkDevice
}
}
}
Instead of using Segue, you can also try doing it with code, i.e.
#IBAction func login(_ sender: UIButton)
{
SparkCloud.sharedInstance().login(withUser: email, password: password) {
if let _ = error
{
print("Wrong credentials or no internet connectivity, please try again")
}
else
{
print("Logged in")
var myPhoton : SparkDevice?
SparkCloud.sharedInstance().getDevices { (devices:[SparkDevice]?, error:Error?) -> Void in
if let _ = error
{
print("Check your internet connectivity")
}
else
{
if let d = devices
{
for device in d
{
myPhoton = device
print(myPhoton!)
//HERE..!!!!!
DispatchQueue.main.async {[weak self] in
let anotherController = self.storyboard?.instantiateViewController(withIdentifier: "AnotherVC") as! AnotherVC
anotherController.myPhoton = myPhoton
self.navigationController?.pushViewController(anotherController, animated: true)
}
}
}
}
}
}
}
}
In the above code, if you want to push the controller, then use:
self.navigationController?.pushViewController(anotherController, animated: true)
otherwise, if you want to present the controller, then use:
self.present(anotherController, animated: true, completion: nil)
Let me know if you still face any issues.

Open different new view controllers by clicking different elements in table view cell - Swift 3

My table view cell displays an entity with two different button elements. I want to be able to launch a view controller that displays a selection of food items if I click on the first button and a different view controller that displays a selection of beverages when I click on the second button.
I am able to correctly pass the data to the new view controllers, but can't seem to dismiss the current view and load the new one. My code is like this:
In the table view cell
#IBAction func foodBtnPressed(_ sender: Any) {
print("foodBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Food", forCustomer: customer!)
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
print("beverageBtn pressed")
print("customer is \(customer?.name)")
vc.loadChooserScreen(toChoose: "Beverage", forCustomer: customer!)
}
In the table view controller
func loadChooserScreen(toChoose: String, forCustomer: Customer) {
print("Choose \(toChoose)")
print("For \(forCustomer.name)")
if toChoose == "Food" {
let foodVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "foodMenu") as? FoodVC
foodVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else if toChoose == "Beverage" {
let beverageVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "beverageMenu") as? BeverageVC
beverageVC?.loadCustomerToEdit(customer: forCustomer)
dismissVC(sender: Any.self)
}
else {
// do nothing
}
}
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
dismiss(animated: true, completion: {
self.delegate!.dismissViewController()
})
}
In this view controller I also have the following protocol
protocol OrderVCProtocol {
func dismissViewController()
}
and have defined
var delegate: OrderVCProtocol!
In my root view controller
func dismissViewController() {
print("Reached dismissViewController function in rootView")
if let foodVC = self.storyboard?.instantiateViewController(withIdentifier: "foodMenu") {
self.present(foodVC, animated: true, completion: nil)
}
if let beverageVC = self.storyboard?.instantiateViewController(withIdentifier: "beverageMenu") {
self.present(beverageVC, animated: true, completion: nil)
}
}
And the delegate is set when the table view controller is called here
#IBAction func loadOrderView(_ sender: Any) {
let orderVC = self.storyboard?.instantiateViewController(withIdentifier: "orderView") as! OrderVC
orderVC.delegate = self
self.present(orderVC, animated: true, completion: nil)
}
Within my target view controllers I have the following function
func loadCustomerToEdit(customer: Customer) {
self.customerToEdit = customer
print("IN FoodVC THE CUSTOMER TO EDIT IS \(self.customerToEdit.name)")
}
and a corresponding one in the BeverageVC.
When I run the app, no errors are thrown and I get the following sample output in the console from my print statements:
foodBtn pressed
customer is Optional("John")
Choose Food
For Optional("John")
IN FoodVC THE CUSTOMER TO EDIT IS Optional("John")
Reached dismissVC function in selectionMenu
and a corresponding response if the beverage button is clicked.
Then nothing happens. So I know the data is correctly being passed to the new view controllers but I don't know how to dismiss the current screen and display the new one with the choices.
I hope my question is clear enough? I'm not sure what's wrong, but the console output clearly shows that the code runs fine until it tries to dismiss the current view.
EDITED TO ADD:
If I modify my dismissVC function in my tableview controller like this:
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
delegate.dismissViewController()
}
the console view now throws
fatal error: unexpectedly found nil while unwrapping an Optional value
And if I modify it again to the following, It goes back to throwing no errors and getting stuck at the same place (i.e. printing the line "Stuck where delegate dismisses view"), showing that the delegate is still nil... but why is it nil when I'd set it in the root view and loaded it in this view?
func dismissVC(sender: Any) {
print("Reached dismissVC function in selectionMenu")
if delegate != nil {
delegate?.dismissViewController()
} else {
print("Stuck where delegate dismisses view")
}
I have solved my problem by implementing notifications via notification centre and delegates. Firstly, in my AppDelegate file I added this line at the bottom
let notifyCnt = NotificationCenter.default
Next, I modified my tableview cell functions to this
#IBAction func foodBtnPressed(_ sender: Any) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Food", "forCustomer": customer])
}
#IBAction func beverageBtnPressed(_ sender: UIButton) {
notifyCnt.post(name: NSNotification.Name(rawValue: "toChoose"), object: nil, userInfo: ["toChoose": "Beverage", "forCustomer": customer])
}
Then, in the tableview controller I modified it to this:
protocol ChooserViewDelegate: class {
func loadChooserView(choice: String, forCustomer: Customer)
}
and defined
weak var delegate: ChooserViewDelegate?
and added this within my ViewDidLoad section
notifyCnt.addObserver(forName: Notification.Name(rawValue: "toChoose"), object: nil, queue: nil, using: loadChooserScreen)
and finally modified my chooser function like so:
func loadChooserScreen(notification: Notification) {
guard let userInfo = notification.userInfo,
let toChoose = userInfo["toChoose"] as? String,
let planToEdit = userInfo["customer"] as? Customer else {
print("No userInfo found in notification")
return
}
delegate?.loadChooserView(choice: toChoose, forCustomer: customer)
}
Then in my root view controller I have the following to replace what I had earlier:
/*Conform to ChooserViewDelegate Protocol */
func loadChooserView(choice: String, forCustomer: Customer) {
self.customer = forCustomer
dismiss(animated: false, completion: {
if choice == "Food" {
self.performSegue(withIdentifier: "food", sender: self.customer)
}
if choice == "Beverage" {
self.performSegue(withIdentifier: "beverage", sender: self.customer)
}
})
}
and I send over the data via prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "food" {
if let foodVC = segue.destination as? FoodVC {
storyboard?.instantiateViewController(withIdentifier: "food")
foodVC.customerToEdit = self.customerToEdit
foodVC.delegate = self
}
}
if segue.identifier == "beverage" {
if let beverageVC = segue.destination as? BeverageVC {
storyboard?.instantiateViewController(withIdentifier: "beverage")
beverageVC.customerToEdit = self.customerToEdit
beverageVC.delegate = self
}
}
}
So now everything loads and views correctly :)

Sending SMS from Contacts Fails

I am implementing a seemingly trivial and very popular use case where users select a contact, and send them a precomposed SMS.
However, the SMS ViewController dismisses itself automatically. This is easily reproducible.
How do I fix this?
Here's my code:
import UIKit
import MessageUI
import ContactsUI
class ViewController: UIViewController, MFMessageComposeViewControllerDelegate, CNContactPickerDelegate{
let contactPickerViewController = CNContactPickerViewController()
let messageVC = MFMessageComposeViewController()
override func viewDidLoad() {
super.viewDidLoad()
contactPickerViewController.delegate = self
messageVC.messageComposeDelegate = self
}
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
picker.presentViewController(messageVC, animated: true, completion: nil)
}
}
}
func messageComposeViewController(controller: MFMessageComposeViewController, didFinishWithResult result: MessageComposeResult) {
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func invite(sender: AnyObject) {
self.presentViewController(self.contactPickerViewController, animated: true, completion: nil)
}
}
The problem is that you are asking your picker to present the message view controller. When contactPicker:picker:didSelectContact: method is called, the picker view controller is automatically being dismissed by the system. This means that the view controller is going away and you are trying to use that view controller to present your next view controller.
What you need to do is have "ViewController" in this case present the message view controller. Below is an example of the portion of your code i changed. You'll notice i have a timer there. This is because if you try to present the messageVC right away, nothing will happen because the contacts view controller isn't done dismissing itself yet.
func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) {
if let phoneNumberValue = contact.phoneNumbers.first?.value as? CNPhoneNumber {
if let phoneNumber = phoneNumberValue.valueForKey("digits") as? String {
// Configure message ViewController
messageVC.recipients = [phoneNumber]
messageVC.body = "Yoyoyo"
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.presentViewController(self.messageVC, animated: true, completion: nil)
})
}
}
}

Resources