I have two view controllers. I want to pass data from mainVC to detailVC during preparing segue. What's better option: calling and pass data to secondVCpresenter (which update View) or directly pass data to secondVC?
class MainVC: UIViewController {
var dataToSend = [Data]
.
.
.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? DetailVC {
destVC.detailPresenter.setData(data: dataToSend)
}
class DetailVC: UIViewController {
lazy var detailPresenter = DetailPresenter(detailDelegate: self)
var newData = [Data]
extension DetailVC: DetailDelegate {
func setData(data: [Data]) {
newData = data
}
}
protocol DetailDelegate: class {
func setData(data: [Data])
}
class DetailPresenter {
weak var detailDelegate DetailDelegate?
init(detailDelegate: DetailDelegate) {
self.detailDelegate = detailDelegate
}
func setData(data: [Data]) {
detailDelegate?.setData(data: data)
}
}
I wonder if it is ok to call detailPresenter from prepare segue in MainVC and if it is not too dirty way to send data?
I agree with Kudos, in this case there's no need for a delegate to pass data to DetailVC as it's already referenced. Using a delegate for this has made it unnecessarily complicated. So yes calling detailPresenter is quite a dirty way of doing things.
Related
I know this may be a simple solution but I can't figure it out. I'm trying to load data from Firestore in my Base ViewController to then populate two Container Viewcontrollers at the same time.(I want to do it this way to save on Read Cost) I've been trying to go the segue route but the segue is called before my data is finished loading from Firestore. I need the data to be present to popular the two different Container Viewcontrollers(One container is a chart. The other container is a line graph). Any suggestions would be greatly appreciated.
import UIKit
import Firebase
class BaseViewController: UIViewController {
var db: Firestore!
override func viewDidLoad() {
super.viewDidLoad()
let settings = FirestoreSettings()
Firestore.firestore().settings = settings
db = Firestore.firestore()
if Reachability.isConnectedToNetwork(){
print("Internet Connection Available!")
loadFirestoreData()
} else {
print("Internet Connection not Available!")
}
}
// Load Firestore Data
func loadFirestoreData() {
db.collection("chartGraph").document("companyX")
.getDocument{ (document, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.performSegue(withIdentifier: "ChartSegue", sender: document!.data()!)
self.performSegue(withIdentifier: "LineGraphSegue", sender: document!.data()!)
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if identifier == "ChartSegue" {
let vc1 = segue.destinationViewController as? ChartViewController
vc1.dataLoaded(data: (sender as? [String: Any])!)
}
if identifier == "LineGraphSegue" {
let vc2 = segue.destinationViewController as? LineGraphViewController
vc2.dataLoaded(data: (sender as? [String: Any])!)
}
}
}
You need to keep a reference to Container in your MainViewController.
For that you should add instance variables to MainViewController that will hold a reference to the container controllers, not just the view. You'll need to set it in prepareForSegue.
So the beginning of MainViewController look something like this:
class MainViewController: UIViewController {
var containerViewChartController: ChartViewController?
var containerViewLineGraphController: LineGraphViewController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let controller = segue.destination as? ChartViewController {
containerViewChartController = controller
} else if let controller = segue.destination as? LineGraphViewController {
containerViewLineGraphController = controller
}
}
then you can call container methods like this
func button_Container() {
containerViewChartController?.changeData(yourData)
}
I want to send my UserModel with all user informations from a ViewController (ShowUserViewController) to another ViewController (ChatViewController) with a delegate but its not working.
In my ShowUserViewControllers user are all informations I want to send to the ChatViewController.
var user: UserModel?
In my ChatViewController I have the following declaration where I want to send my datas:
var currentUser: UserModel?
Here my protocol:
protocol UserInfoToChatID {
func observeUserID(user: UserModel)
}
Here I prepare the segue and set delegate by tapping the button:
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
var delegate: UserInfoToChatID?
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
delegate?.observeUserID(user: user!)
}
At last I call the delegate in my ChatViewController:
extension ChatViewController: UserInfoToChatID {
func observeUserID(user: UserModel) {
self.currentUser = user
performSegue(withIdentifier: "UserInfoToChatVC", sender: self)
}
}
If you need to pass data from one ViewController to another, you don't have to use delegates for this. You can just pass this data as sender parameter of performSegue method:
performSegue(withIdentifier: "UserInfoToChatVC", sender: user!)
then in prepare for segue just downcast sender as UserModel and assign destination's currentUser variable
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = sender as! UserModel
}
}
But in your case you actually don't have to pass user as sender. You can just assign destination's currentUser variable as ShowUserViewController's global variable user
...
} else if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = user!
}
}
2 things:
first, if you just want to pass data from one viewController to other viewController you don't need to use delegate pattern, just pass the object to the next viewController on prepare form segue.
second, if you want to implement the delegate pattern you should have one viewController than call to the delegate and the other implement the functions.
example:
protocol ExampleDelegate: class {
func delegateFunction()
}
class A {
//have delegate var
weak var delegate: ExampleDelegate?
// someWhere in the code when needed call to the delegate function...
delegate?.delegateFunction()
}
Class B: ExampleDelegate {
func delegateFunction() {
// do some code....
}
//when you move to the next viewControoler(to A in that case)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "AClass" {
if let vc = segue.destination as? A {
vc.delegate = self
}
}
}
To pass the UserModel object forward, from ShowUserViewController to ChatViewController, you should use something called Dependency Injection:
So you'll do something like this inside ShowUserViewController:
#IBAction func chatButtonTapped(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "UserInfoToChatVC", sender: nil)
}
Note: The sender parameter should be the object that initiated the segue. It could be self, i.e. the ShowUserViewController object, but I'd advise against passing the UserModel object, because that object did not initiate the segue, and has nothing to do with navigation at all. It should be injected inside the Destination Controller later on.
In the same file, override the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
}
}
I believe you've mostly done this part right, but you may need to communicate back from ChatViewController to ShowUserViewController.
In that case, you can and should use Delegation.
Create something like this inside ShowUserViewController:
protocol ChatViewControllerDelegate: class {
func didUpdateUser(_ model: UserModel)
}
class ChatViewController: UIViewControler {
var user: UserModel?
weak var delegate: ChatViewControllerDelegate?
/* more code */
func someEventHappened() {
delegate?.didUpdateUser(self.user!)
}
}
Finally, there is an additional line to be added to the prepare(for:) method:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "UserInfoToChatVC" {
let chatVC = segue.destination as! ChatViewController
chatVC.currentUser = self.user
// Add this line...
chatVC.delegate = self
}
}
And specify that the ShowUserViewController implements the ChatViewControllerDelegate protocol, then override the didUpdateUser(_:) method:
func didUpdateUser(_ model: UserModel) {
// Some code here
}
I'm having a problem passing array data back from one view controller ("VC2") to another ("VC1"). I do everything by the rules. I made a proper protocol in VC1.
But unfortunately I could not get the data back.
This is my code:
VC2
protocol RecivedData {
func dataRecived(nameArray: [String] , priceArray: [String])
}
var popUpdelegate : RecivedData?
#IBAction func nextBtnTapped(_ sender: UIButton) {
print("Hello")
let namedata = itemNameArr
let namePrice = itemPriceArr
self.popUpdelegate?.dataRecived(nameArray: namedata, priceArray: namePrice)
print(namedata)
print(namePrice)
self.view.removeFromSuperview()
}
VC1
class HomeVC: UIViewController , RecivedData {
func dataRecived(nameArray: [String], priceArray: [String]) {
itemNameArr += nameArray
itemPriceArr += priceArray
print(itemNameArr, itemPriceArr)
print ("This is HomeVC")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sendSegue"{
let secondVC: AddOnItemPopUpVC = segue.destination as! AddOnItemPopUpVC
secondVC.popUpdelegate = self
}
}
}
Replace your code with this
protocol RecivedData : class {
func dataRecived(nameArray: [String] , priceArray: [String])
}
And
weak var popUpdelegate : RecivedData?
Now it will start working.
Make sure there will be no typo in segue name.
Lately I have been working on a new app and I'm struggling to pass data between view controllers.
I use a view controller which holds a container view in which I use google maps. As some of you know it's impossible to put a button on google maps as it overlays everything (that why I put it as a container view). Now I got a problem that I can't make an action button that can performSegue and I also failed to pass an object with the NotificationCenter. I made a model called Song and I want to pass a song List to other controller (a table view).
class Song {
var sid: String
var songName: String
var artistId: String
var length: String
var songImage: String?
var album: String
var lastUpdate:Date? }
Any ideas or suggestions to move this list between VCs?
There is no real connection between those views though, the mainVC is holding them both as containers.
So you have MainController that own both of your controllers that had to exchange data? On way is to use Delegate pattern and setup weak relationsships between your controllers:
protocol SongSelectable {
func songSelected(_ song: Song)
}
class PlaylistController: UIViewController {
weak var songSelectable: SongSelectable?
}
class MapController: UIViewController, SongSelectable {
func songSelected(_ song: Song) {
//do your code
}
}
class MainController: UIViewController {
weak var mapController: MapController?
weak var playlistController: PlaylistController?
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// You can't guaranty order here, so some duplication required
if let controller = segue as? MapController {
mapController = controller
playlistController?.songSelectable = controller
}
else if let controller = segue as? PlaylistController {
playlistController = controller
controller.songSelectable = mapController
}
}
}
Another option: establish connection via blocks. It is more modern and Swifty way:
typealias SongHandler = (Song)->()
class PlaylistController: UIViewController {
var songHandler = SongHandler?
}
class MapController: UIViewController {
func songSelected(_ song: Song) {
//do your code
}
}
class MainController: UIViewController {
weak var mapController: MapController?
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue as? MapController {
mapController = controller
}
else if let controller = segue as? PlaylistController {
controller.songHandler = { [unowned self] (song) in self.mapController?.songSelected(song)
}
}
}
Mind that code fragments above is just illustrations, you have plenty of ways to access controllers, setup delegation or blocks.
I have an ios app in swift and I have a UIViewController (let's call it parentController) with a container. This container embeds another UIViewController called embedController.
embedController contains a method that prints a message to a console.
How can I call this method from my parentController?
I tried to use protocols, my current code is as follows:
class ParentController: UIViewController {
var handleEmbedController:HandleEmbedController?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
embed.value1 = value1
}
}
#IBAction func sendMsgButtonAction(sender: AnyObject) {
handleEmbedController?.printMsg() //this so far does nothing
}
}
and my embedController:
protocol HandleEmbedController: class {
func printMsg()
}
class EmbedController: UITableViewController, HandleEmbedController{
var value1 = ""
func printMsg(){
print("printing some embedded message")
}
}
How can I print this message from a parent controller?
What are you doing in your prepare for segue? Aren't you supposed to set your delegate (protocol) there? Like this:
if (segue.identifier == "embedViewSegue"){
if let embed = segue.destinationViewController as? EmbedController {
self.handleEmbedController = embed
}
}
If you put a breakpoint in sendMsgButtonAction you should see that the property handleEmbedController is nil. And that's why the method call does nothing since you are safely unwrapping it with ?.