I had a working app with the CBCentralManager and CBPeripheral components in the same view controller, but now wish to separate the logic so that I can have a separate connection screen. My plan was to create the CBCentralManager on the Connection Page, discover & connect the peripheral, segue to the Dashboard page, and then use the CBPeripheral there.
My code (stripped down) is as follows:
var globalBTDevice : CBPeripheral! // Only using this as a global variable because I can't get this to pass using prepareForSegue
class ConnectionPageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, CBCentralManagerDelegate {
var centralManager : CBCentralManager!
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
globalBTDevice = self.allFoundDevices[indexPath.row]
centralManager.stopScan()
self.performSegueWithIdentifier("connectedPeripheralSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "connectedPeripheralSegue" {
let destinationVC = segue.destinationViewController as DashboardViewController! // ERROR here: cannot convert value of type "UIViewController" to type "DashboardViewController!" in coercion.
globalBTDevice.delegate = destinationVC
}
centralManager.connectPeripheral(globalBTDevice, options: nil)
}
}
and
class DashboardViewController: UIViewController, CBPeripheralDelegate {
// All delegate methods implemented here
}
I have a segue set up between the 2 view controllers with Identifier "connectedPeripheralSegue".
Also, the DashboardViewController is actually for a tab of a TabBarController - not sure if this makes a difference.
So the issue I get is that I can't cast the destination view controller as a DashboardViewController on the line marked ERROR. It seems to be caused by the VC implementing the CBPeripheralDelegate Protocol, as if I remove that, then I can cast (however that makes the code useless, as I need this in that class). If I cast to UIViewController instead of DashboardViewController, then setting the delegate on the next line fails with "Cannot assign value of type "UIViewController!" to type "CBPeripheralDelegate?" (which makes sense).
I'm totally out of ideas on how to fix this. Can anyone help?
Thanks!
You should use as? operator for optional typecasting. Below code should work,
let destinationVC = segue.destinationViewController as? DashboardViewController
Related
I'm trying to send data from one ViewController to another with delegate, but can't seem to get the right instance
I've tried setting the delegate at different places within the receiving ViewController including ViewDidLoad, but the delegate in the sending ViewController is always nil.
From what I've learned, it's an average problem everybody seems to go through, and I've read quite a number of samples, tried them, but to no avail. I don't know if I'm leaving something out or not. Please shed some light if you will.
Below is what I ended up with.
The sending ViewController:
protocol CreateChatDelegate: class{
func appendChatData(_ sender: CreateChatViewController)
}
class CreateChatViewController: UIViewController {
weak var delegate: CreateChatDelegate!
#IBAction func createChat(_ sender: AnyObject) {
delegate?.appendChatData(self)
if delegate == nil {
print("delegate unsuccessful")
} else {
print("delegate successful")
}
}
The receiving ViewController:
class ViewController: UIViewController{
var createChatViewController: CreateChatViewController!
override func viewDidLoad() {
super.viewDidLoad()
...
}
}
extension ViewController: CreateChatDelegate {
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// get the reference to the ViewController
self.createChatViewController = segue.destination as? CreateChatViewController
// set it as delegate
self.createChatViewController?.delegate = self
print("ViewController: delegate successful")
}
}
func appendChatData(_ sender: CreateChatViewController) {
print("ViewController: CreateChatDelegate called")
}
}
this code outputs "delegate unsuccessful", because delegate is always nil
The method you are using is incorrect. You should use the new one:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
....
}
Notice the override keyword? if you don't see this when you are writing the viewController methods, It means that you are NOT calling the original method and misspelled the function signature.
NOTE: if you are targeting older iOS and using older Xcode, the method name may be different, you should write the name of the method and let the AutoComplete help you with the correct one.
To successsfuly configure segue you need to make sure that
1- Navigation is triggered by
self.performSegue(withIdentifier:"segue",sender:nil)
2- Force-unwrap
self.createChatViewController = segue.destination as! CreateChatViewController
as as? cast may fail silently for some reason
First make sure that prepareForSegue() method is called. Then make sure that it is called for the CreateChatViewController i.e.
if let destination = segue.destination as? CreateChatViewController {
//Do all the stuff there e.g.
destination.delegate = self
}
If your prepareForSegue() method is not called then set the action properly so it will fire the prepareForSegue() method then you will get the delegate value in the CreateChatViewController.
I have two VCs in my project. I have a UIButton that segues to the second VC. I have data being sent to this VC. I want the second VC to be able to add to the array that is sent and then send it back.
In my main VC I have:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toViewController = segue.destination as! SaveViewController
toViewController.masterView = self
In the second VC I have:
var masterView:UIViewController!
...
override func viewWillDisappear(_ animated: Bool) {
masterView.listArray = listArray
}
What I am getting is
Value of type 'UIViewController' has no member 'listArray.'
The listArray is declared in both VCs. If this is a correct way to go about doing what I am trying to do, I am obviously assuming that I must do some more configuring in the second ViewController in order to make the other VC accessible.
Probably this is not the right way to pass data back the the previous view controller. Although there are other options that you can follow to achieve the desired functionality, I would recommend to follow the Delegation pattern approach.
For your case, you could do it like this -for instance-:
According to "How to Apply Delegation?" section in this answer, the first thing that we should do is to implement the needed protocol:
protocol SaveViewControllerDelegate: class {
// I assumed that 'listArray' is an array of strings, change it to the desired type...
func saveViewControllerWillDisappear(_ listArray: [String], viewController: UIViewController)
}
Thus in SaveViewController, you should create -weak- instance of SaveViewControllerDelegate and call its method at for the desired behavior:
class SaveViewController: UIViewController {
weak var delegate: SaveViewControllerDelegate? = nil
var listArray: [String]!
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// assuming that you already did the required update to 'listArray'
// you would need to pass it here:
delegate?.saveViewControllerWillDisappear(listArray, viewController: self)
}
}
So far we added the necessary code for the SaveViewController, let's jump the the MasterViewController (first view controller):
Next, you would need to conform to SaveViewControllerDelegate, Connecting the delegate object and implement its method (steps from 2 to 4 in the mentioned answer):
class MasterViewController: UIViewController, SaveViewControllerDelegate {
var listArray: [String]!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let toViewController = segue.destination as! SaveViewController
// make sure to add this:
toViewController.delegate = self
toViewController.listArray = self.listArray
}
func saveViewControllerWillDisappear(_ listArray: [String], viewController: UIViewController) {
print("here is my updated array list: \(listArray)")
}
}
At this point, saveViewControllerWillDisappear method should be get called when coming back from SaveViewController, including listArray as a parameter.
Aside note:
The reason of the error that you are facing is that you are declaring masterView as UIViewController, what you should do instead is:
var masterView:MasterViewController!
HOWEVER keep in mind that this approach still -as I mentioned before- inappropriate one.
This happens because UIViewController has no element listView.
Change MasterView type to:
var masterView: FirstViewController!
I've been looking into how delegation works. You define a protocol in controller A, create a delegate variable, and call the function through the delegate. Then, in controller B, you conform to the protocol, implement methods, and then use prepareForSegue to tell controller A that controller B is the delegate.
But this involves A -> B -> A. I need to know how to do A -> B. I've been trying to do this through the following code:
Declare the protocol in controller A
protocol CellDataDelegate {
func userDidTapCell(data: String)
}
Create a delegate variable in A
var cellDelegate: CellDataDelegate? = nil
Call the function in the delegate in A when cell tapped
if cellDelegate != nil {
let cellKey = keys[indexPath.row].cellKey
cellDelegate?.userDidTapCell(data: cellKey)
self.performSegue(withIdentifier: "showDetails", sender: self)
}
Add the delegate to controller B and conform to the method
class DetailsVC: UIViewController, CellDataDelegate
The function:
func userDidTapCell(data: String) {
useData(cellKey: data)
}
The problem here is the last part of the delegation process. I can't use prepareForSegue to do the controllerA.delegate = self part because I don't want to go back to controller A, I need to stay in controller B. So how do I tell controller A that B is the delegate?
Protocol Delegates are usually used to pass data to a previous UIViewController than the present one in the navigation stack(in case of popViewController) because the UIViewController to which the data is to be sent needs to be present in the memory. In your case you havn't initialised UIViewController B in memory for the method of protocol delegate to execute.
There are simple ways to send data to the next UIViewControllers in the navigation stack.
Your UIViewController B should have a receiving variable to store data sent from the UIViewController A
class DestinationVC : UIViewController
{
receivingVariable = AnyObject? // can be of any data type depending on the data
}
Method 1: Using Storyboard ID
let destinationVC = self.storyboard.instantiateViewControllerWithIdentifier("DestinationVC") as DestinationVC
destinationVC.receivingVariable = dataInFirstViewControllerToBePassed
self.navigationController.pushViewController(destinationVC , animated: true)
Method 2: Using prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!)
{
let destinationVC = segue.destinationViewController as DestinationVC
destinationVC.receivingVariable = dataInFirstViewControllerToBePassed
}
Multiple segues from UIViewController A to any other UIViewController will cause in execution of prepareForSegue every single time and might crash the application as other classes of UIViewControllers would have no such parameters as receivingVariable which is present in UIViewController B.
This can be easily countered; use of multiple segues can be done simply using if else or switch modules on segue.identifier which is a parameter of segue.
Note: UILabel, UIButton and another other UI element's attribute cannot be assigned in this manner because these element load in the memory in the func loadView() of UIViewController lifecycle as they are not set to initialise when you initialise the class of UIViewController B as mentioned above.
I don't think you need to use delegate pattern here. If you are trying to achieve this. You have some cells on view controller A and now you want to display details of cell(on click) in view controller B. You can declare cell key as the property in view controller B.
class B: UIViewController {
let cellKey: String!
}
And set the above key in prepare for segue method
if (segue.identifier == "segueToViewControllerB") {
let vc = segue.destinationViewController as B
vc.cellKey= "1"
}
I think you are misunderstanding the point of the question you referenced. The question above explained the what is happening in a lot of detail, but here is a short answer, for those who are lazy: do NOT you prepareForSegue to pass information bottom to top (i.e. from child view controller to parent), but most certainly DO use it to pass top to bottom.
Hello learning swift and am stuck with calling a method through delegate. Checked multiple answers with similar issues and have tried the solutions but have not been able to successfully apply them to my own situation however I am close.
I have a delegator class named ViewController that holds a variable I would like to change. I have another view called MoodScroll which serves as the delegate. Moodscroll has a button being used to change the value for the variable in ViewController.
ViewController :
class ViewController: UIViewController, AVAudioPlayerDelegate, MoodScrollDelegate {
var alarmSoundType: String?
func acceptData(data: String?) {
alarmSoundType = "\(data)"
print(data)
}
}
MoodScroll :
protocol MoodScrollDelegate {
func acceptData(data: String?)
}
import UIKit
class MoodScroll: UIViewController {
#IBAction func WTF(sender: AnyObject) {
self.delegate?.acceptData("hello")
print("function called")
}
}
The IBAction calls fine as it prints "function called" in the console however it doesn't pass the value to ViewController as alarmSoundType remains nil and also the print command is not called in ViewController as well.
It seems you still have some confusion about delegation : if ViewController conforms to MoodScrollDelegate protocol, then your ViewController object will be the delegate, not the MoodScroll object.
Where do you set the delegate property of your MoodScroll object ?
If this object is created programmatically from your ViewController object, you should set it after initialization :
myMoodScrollObject.delegate = self
Is the object is created using Interface Builder, you can either use an outlet variable for delegate, or set it in prepareForSegue:sender of your ViewController class :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let scroll = segue.destinationViewController as? MoodScroll{
scroll.delegate = self
}
}
One picky note: the way you have described your problem, it's actually ViewController what you should call the delegate of MoodScroll. Most likely you're probably forgetting to set the delegate property of MoodScroll.
I don't know how these two view controllers relate to each other in your code, but very often you would set the delegate property in the prepareForSegue method of ViewController, for example:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueToMoodScroll" {
let moodScrollController = segue.destinationViewController as! MoodScroll
moodScrollController.delegate = self
}
}
I have a CoreData project where I've got a FilterViewController set up and I want to display the results in a DisplayResultsViewController.
I know it's something to do with the delegate, but I'm stumped how to fix it. I know I'm not the first person to move data from one VC to another, but I can't figure out the answer.
// Top of FilterViewController
import UIKit
import CoreData
protocol FilterViewControllerDelegate: class {
func filterViewController(filter: FilterViewController,
didSelectPredicate predicate:NSPredicate?,
sortDescriptor:NSSortDescriptor?)
}
// Predicates set here
// IBAction to trigger the segue
#IBAction func filter(sender: UIBarButtonItem) {
delegate!.filterViewController(self,
didSelectPredicate: selectedPredicate,
sortDescriptor: selectedSortDescriptor)
dismissViewControllerAnimated(true, completion:nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toDisplaySearchResults" {
let navController = segue.destinationViewController as UINavigationController
let filterVC = navController.topViewController as StretchSelectorViewController
filterVC.coreDataStack = coreDataStack
filterVC.delegate = self // **ERROR here
}
}
You haven't shown the code that declares the class containing the method that fails (and thus determines what self is) so we're missing the key line of code. But in general, your view controller should be declared something like:
class MyViewController: UIViewController, ViewControllerDelegate {
....
}
... where the first class after the colon (UIViewController) indicates the superclass and the following subsequent items(s) identify protocols to which your class is declaring conformance. The error indicates that your class doesn't conform to the ViewControllerDelegate protocol, so it must be missing that declaration.