Ok, Ive tried the usual methods here but can't figure out this relatively simple issue -
I have a view controller that I present here:
self.presentViewController((self.storyboard?.instantiateViewControllerWithIdentifier("createEvent"))!, animated: true, completion: nil)
And I need to call a function in my original main VC from this createEvent VC. I have tried putting:
weak var superv: MainViewController!
in the create event class then doing something like this:
let create = self.storyboard?.instantiateViewControllerWithIdentifier("createEvent")
create.superv = self //error here
Then in create doing:
superv.updateThings()
But I get an error trying to tie the presented VC to my MainViewController. What am I doing wrong here?
I have seen use of protocols but I would like to avoid that. What is the simplest way to do this?
This is what I did:
In main:
var create = UIViewController()
create = (self.storyboard?.instantiateViewControllerWithIdentifier("createEvent"))!
create.delegate = self //error here
In create:
weak var delegate: CreateEventDelegate!
protocol CreateEventDelegate: class {
func doSomeFunction ()
}
And the error is value of type uiviewcontroller has no member delegate
The function in main I need to call is:
self.tableView.reloadData()
Error:
Use protocols. I know you said you don't want to but its so simple and convenient.
In your createEvent vc:
weak var delegate: CreateEventDelegate!
Then at the bottom of the file (outside the create event VC class)
protocol CreateEventDelegate: class {
func doSomeFunction ()
}
Then in the main VC class conform to the CreateEventDelegate protocol and add this:
create.delegate = self
Then you can call delegate.doSomeFunction() in your create event VC
Related
My sender class for delegation:
import UIKit
protocol tapDelgation:class {
func tapConfirmed(message:String)
}
class ViewController: UIViewController {
weak var delegate:tapDelgation?
#IBAction func deligateSenderAction(_ sender: Any) {
var data = "hello world"
print(data)
self.delegate?.tapConfirmed(message: data)
}
}
My reciever class:
import UIKit
class NextViewController: UIViewController {
weak var vc:ViewController? = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
vc?.delegate = self
}
}
extension NextViewController : tapDelgation {
func tapConfirmed(message: String) {
print(message)
}
}
What is expected: A button on sender vc is pressed and from reciever vc a console print would be popped. But in vain, nothing happens. Does any one know why it is happening? If it is not possible then why?
It looks like a memory management problem to me.
First problem: Creating a view controller with a default initializer like ViewController() is almost never the right thing to do. because it won't have any view contents.
You don't explain how your NextViewController and your ViewController get created and displayed.
It looks like NextViewController has a weak reference to ViewController, and ViewController's delegate point is also weak (delegate references should almost always be weak.)
This line:
weak var vc:ViewController? = ViewController()
Will cause NextViewController to create an instance of ViewController that isn't owned by anybody, so it will immediately be deallocated and the vc variable will go back to being nil. By the time you get to NextViewController's viewDidLoad, vc will be nil, so the optional binding in the line vc?.delegate = self won't do anything.
NextViewController's vc variable should almost certainly be a strong reference, not weak, but you don't show how ViewController ever gets displayed to the screen, so it isn't clear what you're trying to do.
weak var vc:ViewController? = ViewController()
Remove weak if you don't set the vc somewhere else and any other instance doesn't keep a strong reference to it.
If there is another instance with a strong reference, please share the related code.
The answer from the https://stackoverflow.com/users/205185/duncan-c is totally correct unless there is any other code which affects the presentation of the NextViewController and reference to the vc: ViewController
I changed viewController to SenderViewController but no luck and Sender and receiver is connected via navigation controller. i.e. If i press a button on sender a recieve comes via push transition. my aim was to since it is triggered an IBAction then the second view controller would implements the tap confirmed function. thanks for your answer. Learned a lot :)
Due to this comment, you need to implement prepareForSegue() method in your ViewController (original one) and set the vc property of the "next" view controller there instead of = ViewController() in the "next" make the extension on the ViewController:
extension ViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextController = segue.destinationViewController as! NextViewController
nextController.vc = self
}
}
Explanation based on the comment:
You get a new instance of the NextViewController with the new instance of the ViewController instantiated on its init (instead of passing the original instance of ViewController to it). That's where you can ge a strange behaviour with delegation.
weak var vc:ViewController? = ViewController()
Remove weak for vc it will release the view controller memory after disappear
In a MVP structured iOS app, very often I would need to call some functions in UIViewController class in my Presenter.
For example, an UI event is triggered and my presenter has done some business logic and decide to do one or some of the following UI updates
Hide back button
Update Navigation bar title
Pop up an UIAlertController
It's a lot easier and tidier to do the following
func didClickAButton() {
//Some business logic
let vc = mUI as! UIViewController
vc.navigationItem.hidesBackButton = true
vc.title = "New Title"
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
vc.present(alert, animated: true, completion: nil)
}
Than creating a protocol function for every functions of UIViewController class that I'm possibly need.
My question is what would be a good way to handling this.
Edit:
Maybe I wasn't clear about my question, so the below code should explain it better
protocol ViewProtocol {
func hideBackButton()
//Potientially one protocol function for each UIViewController's
//function I might need
}
class Presenter {
weak var mUI: ViewProtocol
func updateUIAfterSomeLogic() {
//At this point, I can do
mUI.hideBackButton()
//or cast mUI to UIViewController because I know it will always
//be a subclass of UIViewController
let vc = mUI as! UIViewController
vc.navigationItem.hidesBackButton = true
}
}
class View: UIViewController, ViewProtocol {
func hideBackButton() {
self.navigationItem.hidesBackButton = true
}
}
The only kind of object that you should be sending UIViewController messages to is an object that is a UIViewController. If you have some other type of object like a Presenter that implements some of the methods of UIViewController but is not a view controller then you should not cast it to a UIViewController.
If your mUI object is a subclass of UIViewController then it already is a UIViewController and there's no reason to cast it to that type.
So no, the code you posted does not seem useful, and if your mUI object is not a subclass of UIViewController then it's more than unhelpful, it's a bad idea.
EDIT:
If mUI is always a ViewController, make it a ViewController that conforms to your protocol:
var mUI: UIViewController & ViewProtocol
Let's say I have a firstViewController and a secondViewController. The first one contains a firstButton and the second one - a secondButton. Here's what I want to do: when user clicks the secondButton, some firstButton's property changes.
Unfortunately, when I create an instance of a firstViewController in a secondViewController and then trying to access a firstButton, I get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
So, technically, I'm trying to do this as follows:
var ins = firstViewController()
#IBAction func secondButtonisPressed(){
ins.firstButton.alpha = 0
}
What is the proper way to implement that?
Thanks in advance.
Your problem here is that the IBOutlets of your firstViewController are only available (!= nil) after the viewDidLoad() firstViewController's method has being called.
In other words, you have to present the view, before you can make any changes to a UIViewController IBOutlet.
How you can solve this?
Add a variable into FirstViewController that works as a flag for you.
for example: var hideFirstButton = false
in the viewDidLoad or viewWillAppear method of FirstViewController check for hideFirstButton's value and hide or show your firstButton.
Then, before you present your FirstViewController change the value of hideFirstButton to the needed for your application to run fine.
UPDATE:
Other workaround, using Storyboard is (This approach has the inconvenient that the completion handler is called after viewWillAppear() so the button is visible for a second):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as! FirstViewController
self.presentViewController(firstViewController, animated: true, completion: {
//This lines will be called after the view is loaded so the code will run
firstViewController.firstButton.alpha = 0
})
EXAMPLE: an example at GitHub
You could try to do this using delegation, similar to the way Apple does it in their existing frameworks. For an example, look at the way that you use UITableViewDelegate when working with a UITableView object.
If you wanted to use delegation to tell secondViewController that firstButton was pressed using delegation, you could do it as follows:
Step 1:
Create a protocol containing a method for the button press event.
protocol buttonPressDelegate {
func buttonPressed() -> Void
}
Step 2:
In firstViewController, declare that you have an instance of an object of type buttonPressProtocol.
var buttonPressDelegateObj: buttonPressDelegate?
Step 3:
In firstViewController, initialize your buttonPressDelegateObj to contain a reference to your instance of secondViewController. If you want you can create a method to set the reference contained in buttonPressDelegateObj, or do it viewDidLoad in firstViewController, etc.
buttonPressDelegateObj = secondViewControllerObj
Step 4:
In secondViewController, declare that you adopt the buttonPressDelegate protocol.
class secondViewController: UIViewController, buttonPressDelegate {
Step 5:
In secondViewController, implement the protocol method buttonPressed() by adding the function with your desired implementation. Here's an example:
func buttonPressed() {
secondButton.alpha = 0
}
Step 6:
Create an #IBAction on the button in firstViewController, so that when the button is pressed it calls buttonPressDelegateObj.buttonPressed() and you can respond to the event
#IBAction func firstButtonPressed() {
if (buttonPressDelegateObj != nil) {
buttonPressDelegateObj.buttonPressed()
}
else {
print("You forgot to set your reference in buttonPressDelegateObj to contain an instance of secondViewController!")
}
}
Note: This is just one way that you could do this. To tell firstViewController that secondButton was pressed (go the other way), have firstViewController implement the protocol buttonPressDelegate, have secondViewController contain a reference to firstViewController as an instance of type buttonPressDelegate?, and create an #IBAction in secondViewController that fires when secondButton is pressed that calls your the buttonPressDelegate method.
Note: There is a similar pattern employed in the Android world to get a Fragment to communicate to an Activity, that you can read more about here
Short explanation.
I have a ContainerViewController that I'm pushing to the navigationStack.
The ContainerViewController has 2 child ViewControllers. A SlidePanelViewController (a slide-out menu) and a CenterViewController (the content)
I have a button in my menu to "sign Out". When this button is clicked I want to push ContainerViewController (and it's 2 childViewControllers) to my LandingPageViewController.
Here's the function I am trying to call:
func signOut() {
println("signOut")
// Set up the landing page as the main viewcontroller again.
let mainTableViewController = LandingPageVC()
mainTableViewController.navigationItem.setHidesBackButton(true, animated: false)
mainTableViewController.skipView = false
self.navigationController!.pushViewController(mainTableViewController, animated: true)
// Disable menu access
menuEnabled = false
// change status bar style back to default (black)
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.Default
}
At first I tried putting this in my SlidePanelViewController. That didn't work. So I put it where I'm assuming it belongs in the ContainerViewController.
However when I click my signOutButton in my menu. I'm presented with the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
When looking into the error. This is the line causing it:
self.navigationController!.pushViewController(mainTableViewController, animated: true)
After the error I checked that the function works, by adding a UINavigationBarButtonItem that called the function (in my ContainerViewController). It did exactly what I wanted.
However when I call this function from my Menu (again my menu is a childViewController of the ContainerViewController). It does not work.
I'm attempting to call it like so:
ContainerViewController().signOut()
I also tried adding a Delegate to my SidePanelViewController like this:
Before the class:
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
optional func toggleLeftPanel()
optional func collapseSidePanels()
}
in viewDidLoad():
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
in my tap gesture function:
func signOutTapGesture() {
println("signOutTapGesture")
selectView(signOutView)
delegate?.needsSignOut?(self)
println(delegate)
}
before my ContainerViewController class:
var leftViewController: SidePanelViewController?
my ContainerViewController class:
class ContainerViewController: UIViewController, CenterViewControllerDelegate, SidePanelViewControllerDelegate, UIGestureRecognizerDelegate {
in my ContainerViewController's viewDidLoad()
leftViewController?.delegate = self
And I changed the signOut function in the ContainerViewController class to this:
func needsSignOut(sender: SidePanelViewController) {
println("needsSignOut called")
self.signOut()
}
However using the delegate like above, doesn't seem to do anything either.
Any help as to How I can successfully push my LandingPageVC from the menu would be greatly appreciated! (I'm not using storyboards)
You're attempting to call signOut with ContainerViewController().signOut(). This will create a new ContainerViewController and because you haven't pushed it onto the navigation controller's stack, navigationController is nil. Try just calling self.signOut(). (I'm assuming signOut in a method of ContainerViewController)
Update - delegates
Your delegate property should go in SidePanelViewController. I'll give you and example of how to implement it:
SidePanelViewController:
(Note - the protocol doesn't have to go here but I think it keeps things organised)
#objc protocol SidePanelViewControllerDelegate {
optional func needsSignOut(sender: SidePanelViewController)
}
class SidePanelViewController: UIViewController {
// Make sure your delegate is weak because if a ContainerViewController owns
// a reference to a SidePanelViewController and the container view controller
// is its delegate, you'll end up with a strong reference cycle!
weak var delegate: SidePanelViewControllerDelegate?
// Called when the UIButton is pressed.
func myButtonWasPressed() {
delegate?.needsSignOut?(self)
}
}
ContainerViewController:
class ContainerViewController: UIViewController {
var sidePanel: SidePanelViewController!
// Setup the side panel...
override func viewDidLoad() {
super.viewDidLoad()
sidePanel.delegate = self
}
func signOut() {
// Sign out stuff here.
}
}
// The ContainerViewController needs to conform to the SidePanelViewControllerDelegate
// protocol if we want the delegate to work. (This could have gone in the initial
// class declaration.)
extension ContainerViewController : SidePanelViewControllerDelegate {
func needsSignOut(sender: SidePanelViewController) {
self.signOut()
}
}
Hope that helps.
The problem seems to be that navigationController is nil and you're trying to force unwrap it (as indicated by your error).
One problem I discussed in my other answer.
Another problem may be you haven't added a navigation controller. To do this you need to:
If you're using Storyboards
You need to make sure you've embedded your UINavigationController. After that, when you use navigationController it won't be nil and you'll be able to push your view controller.
When you're on your storyboard:
Also, if you're using storyboards, have you considered using segues to move around instead of calling presentViewController? I've found it makes everything much easier.
If you're not using Storyboards
Have a look at this post: Programatically creating UINavigationController in iOS
I have this problem:
Im having a custom class named CoredataAction
In this class I do all my CoreData actions and it is not an UIViewController.
How can I open a view from my CoreDataAction class?
I have tryied opening a storyboard but did didn't work! It got me an bad_access error
One solution would be to add a variable in your CoredataAction class that holds the initial ViewController, just make sure you set that variable when you initialise your CoredataAction class.
CoredataAction
class CoredataAction {
var parentViewController:UIViewController!
func presentNewViewController() {
let newViewController = parentViewController.storyboard?.instantiateViewControllerWithIdentifier("YOUR STORYBOARD ID") as UIViewController
parentViewController.presentViewController(newViewController, animated: true, completion: nil)
}
}
ViewController
func initCustomClass() {
var coreData = CoredataAction()
coreData.parentViewController = self
}
Another option is to use a protocol to delegate the view presentation back to the ViewController class itself. It would be a very similar setup to the above, it just means the view presentation logic can be kept out of your CoredataAction customClass - Let me know if you would like an example of this.