How can I call a method that is inside a UIViewController embedded in a container from a parent UIViewController? - ios

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 ?.

Related

Passing data to detailVC in MVP architecture

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.

How to set a delegate in Swift

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
}

Passing an object between view controllers

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.

Can't send data between to view controllers Swift

I have got a tabbed project in my Xcode.But my Entry Point is a separate view controller that is not connected to tabs.So when user clicks Login button i send value of input to one of view controllers in tabbed part of my project.I have a segue between separated VC and VC that I send data to.Here is my code In part where I send the data
protocol SendDel {
func userDidEnterData(data: String)
}
LogInViewController: UIViewController {
var delegate:SendDel!=nil
#IBAction func SendB(_ sender: Any) {
if(delegate != nil){
if(self.Usn != nil){
let data = self.Usn.text
self.delegate?.userDidEnterData(data: data!)
}
}
}
}
And here is code in part where I receive data
class FirstViewController: UIViewController,SendDel {
func userDidEnterData(data: String) {
UsernameLabel.text="\(data)"
dismiss(animated: false, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "First"){
let sendingVc=segue.destination as! LogInViewController
sendingVc.delegate = self
}
}
}
But unfortunately it is not working.
Actually you setting the delegate property from FirstViewController and then when you present the LogInViewController you have write the code var delegate:SendDel!=nil which makes delegate nil every time you tapped the button.
So try below code :
var delegate: SendDel?
Hope it works for you.

Swift: What instance of my class is in the view

I'm building an app with a container view holding a tableView controller. I create this tableView, but I don't know how to access this object again so I can call function on it. Currently there is a BucketTableViewController object being created automatically (maybe from the storyboard). Then later I want to call a function on it and create another BucketTableViewController object. I can verify they are unique with print statement on that method. How do I set a variable for an object that is the original object?
import UIKit
class FirstViewController: UIViewController {
var bigArray = ["M", "A", "R", "C"]
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
reachForWebsite()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func reachForWebsite(){
let url = NSURL(...)
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
do {
...
// HERE IS THE ISSUE
var bucketsVC = BucketTableViewController()
bucketsVC.updateBuckets(self.bigArray)
} catch let myJSONError {
print(myJSONError)
}
}
task!.resume()
}
}
You can grab a reference to it from prepareForSeque(_:sender:) in the view controller that owns the container. Make sure that identifier matches the name of the identifier you've set on the segue from the storyboard in Interface Builder. Or you can omit the identifier part if you know for certain that there are no other segues with destination's of type BucketTableViewController.
class BucketTableViewController: UITableViewController {}
class FirstViewController: UIViewController {
var bucketViewController: BucketTableViewController!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue( segue, sender: sender )
if let vc = segue.destinationViewController as? BucketTableViewController where segue.identifier == "embeddedBuketViewcontroller" {
self.bucketViewController = vc
}
}
}
A comment is too tight for this, so I'm making it an answer. You can make bucketsVC` an instance variable:
class FirstViewController: UIViewController {
var bucketsVS : BucketTableViewController?
func reachForWebsite(){
...
do {
self.bucketsVC = BucketTableViewController()
self.bucketsVC!.updateBuckets(self.bigArray)
} catch {
...
}
// Now you can use it anywhere within your UIViewController
}
}

Resources