UIViewController call function in container view viewcontroller - ios

Using Swift 3. This question has been around and there are quite a few examples. Unfortunately none of them have worked for me. I have a uiviewcontroller that has a container view. This container view has a segue to another view controller. Everything loads up correctly and displays correctly the first time. The problem that I am facing is that I have a button on the main uiviewcontroller which by tapping it, it will refresh the container view. This is where the problem is. The outlets in the child controller are nil.
This is how I am trying to all the function in the child controller.
let wcvc = self.storyboard!.instantiateViewController(withIdentifier: "WeatherCurrentViewController") as! WeatherCurrentViewController
wcvc.callJson()
In the child controller the label is like this
#IBOutlet var currentweather: UILabel!
But in the function that is called I get an error of unexpectedly found nil while unwrapping an Optional value when I try to do
print(currentweather)
Anyone have an idea of what I can do to call a function in a view controller that is loaded by segue in a container view and still have the outlets be valid? Please let me know if more info is needed.
Found the solution. I needed to use segues identifiers and not the storyboard. Doing this
private var currentEmbeddedViewController: WeatherCurrentViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let cvc = segue.destination as? WeatherCurrentViewController, segue.identifier == "CurrentWeather" {
self.currentEmbeddedViewController = cvc
}
}
And calling the function in the controller like so
self.currentEmbeddedViewController.callJson()
Was the solution. This question offered the solution. Sorry to waste everyones time. Hopefully, this will help someone else.

The reason why you get an exception is that your currentweather label will get loaded from the storyboard only when the view of that view controller loads. This means, that when you try to call callJson() the view is not yet loaded and thus no IBOutlets are loaded yet as well, meaning they are nil.
I would suggest passing some data to theWeatherCurrentViewController and only on viewDidLoad updating the views. viewDidLoad guarantees that view and all the outlets are loaded and are not nil

Related

I am trying to pass data from one View Controller to another in Swift via 'prepare for segue' but it crashes

Good evening,
I am trying to pass data from one View Controller to another, but it crashes my program in the final View Controller. Can anyone help me?
This is the ViewController:
func updateAfterPlayAgain(){
labelSay.text = ">Result<"
scoreLabel.text = "Score: 0"
score = 0
seconds = 10
mainButton.isEnabled = true
userResult.isUserInteractionEnabled = true
}
This is the PopUpViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var fate: ViewController = segue.destination as! ViewController
fate.updateAfterPlayAgain()
}
It is from this one that I am trying to access the data from ViewController when it(PopUpViewController) closes. I would like to update the information in the ViewController when I close the PopUpViewController.
Thank you.
When prepareForSegue is called the destination view controllers' views haven't been loaded, so all of it's outlets will be nil.
Outlets are set up as implicitly unwrapped optionals, and that causes a crash if you try to reference an outlet that's nil. That's why you're crashing.
You should not call updateAfterPlayAgain() from prepareForSegue, since it tries to reference your outlets before they are connected. Instead call updateAfterPlayAgain() from viewWillAppear (or better yet from viewDidLoad, since that only gets called once when a view controller's views are first loaded.)
#Banjo, your question is not completely clear. Please add the crash it self so we will have more information ti help you.
From what I see, when you create a screen, the #IBOutlet labels, are still nil until the screen performs the "viewDidLoad" method, therefore, instead of updating the screen in the prepare(for segue), I'd set a flag using a boolean, i.e.: "requiresUpdate: Bool", and set it here, then in the viewDidAppear, check the flag and call updateAferPlayAgain.
If you are talking about dismissing a controller and updating the main controller instead, you should read about how to use "unwind segues" and handling their events, this way the parent controller gets the event of the displayed controller closing.
This article should help you with that:
https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Good luck.

Unable to call embedded segue the second time

I'm trying to build an app similar to Instagram, and I'm stuck at the comments portion. On the left side of the image, it is a VC that has an embedded container view with UITextField and UIButton at the bottom. The container view is embedded with a UITableView that contains all the user profile image, username and the comment itself.
At first load, it can perfectly grab all comments for that post from server side, and displayed perfectly. However, I am unable to call the segue again using prepareForSegue to update the UITableView. I'm receiving an error:
There are unexpected subviews in the container view. Perhaps the embed
segue has already fired once or a subview was added programmatically?
Below are my codes:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "getCommentSegue"){
if let destination = segue.destinationViewController as? CommentsTVC{
if(!self.commentID.isEmpty){
destination.UpdateCommentRow(self.profileImage, commentID: self.commentID, comment: self.postComment, dateTimePost: self.dateTime)
self.commentID = String()
}
destination.postID = self.postID
}
}
}
And after successfully adding a row to my database in the dispatch_async:
self.performSegueWithIdentifier("getCommentSegue", sender: nil)
I've also noticed that when it append new comment to my existing object that stores all the comments, the count for it is 0. I believe it is taking a new reference for the object. Please help!
embed navigationcontroller to container view and then try. It may solve youar problem i think..!!
Remove your container view's subviews before you call performSegue.

Is closing a modal view a type of segue? Can I use prepareForSegue function? Swift/xcode

I´m making an app in Xcode V6 and I´m trying to send a variable to the main viewController when i close my modal view which appeared after I press a button on the main viewController.
I open my app and see storyboard1 with a button1, I click button1 and a storyboard2 appears in modal view. When I close the modal view I want to send a variable created in storyboard2 to storyboard1. It´s the sending I have problems with.
I have this code:
Storyboard2:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var sendValues = "This is a title!"
let destinationValues = segue.destinationViewController as ViewController
destinationValues.recieveString = sendValues
}
Storyboard1
var recieveString: String = ""
println(recieveString)
I try to print it when I press a button but nothing happens..
when I segue between the views I try to print it but nothing is printed, It´s like the segue did´t work...
Any ideas?
The pattern often used in iOS development for this kind of case is delegation. Basically you pass your original (presenter) view controller as an object that conforms to a protocol defined by the presented view controller. Then from the presented view controller you can modify the presenter's variables.

Using delegate between sibling view controllers in containers

I'm trying to make an app that uses three containers to show different content, but I'm having trouble communicating between the containers. I succeeded to use a segue to send some information at the tap of a button in one container to another container, but part of this information also has to be relayed to the third container. For this I wanted to use a delegate, but I cannot reference the right ViewController to the delegate variable.
So what I want goes as follows:
CollectionViewCell tapped, triggering segue to TableVC
TableVC receives information and updates the table
TableVC triggers delegate function in third VC
Third VC takes in some info and updates view
In the above I have managed to get 1 and 2 to work, but got stuck at 3.
I have made my protocol as follows:
protocol PurchaseDelegate {
func addToTotalAmount(product : Product)
}
In the TableVC I have declared var delegate : PurchaseDelegate? = nil and in the IBAction triggered from the segue: delegate?.addToTotalAmount(product)
In the third VC I have implemented the delegate as follows:
class thirdVC:UIViewController,PurchaseDelegate {
func addToTotalAmount(product : Product) {
println("Adding....")
}
}
All three containers are within a main VC that does some initial stuff in the application.
My problem is, that I don't know how to get a reference from thirdVC to my delegate variable in my tableVC.
Thanks in advance.
I ended up finding the solution to the problem after a bit further searching with inspiration from #Anna Dickinson.
Firstly, the containers must be ordered correctly in the storyboard. The container whose view controller implements the delegate protocol must be first in the list and then the other view controller further down.
Then, in the main view controller - the view controller for the view with the containers - the prepareForSegue function is implemented, since it will be triggered as the containers are initialized.
This all of the code remains as above, but the main view controller will be something like the following:
class MainViewController: UIViewController {
var actionVC : FirstViewController! // This is the one, that implements the delegate protocol
var tableVC : SecondViewController! // This is the one, that has a delegate variable
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "firstVC"){
self.actionVC = segue.destinationViewController as FirstViewController
} else if(segue.identifier == "secondVC"){
self.tableVC = segue.destinationViewController as SecondViewController
self.tableVC.delegate = self.actionVC
}
}
}
I'm not sure if the is the right, nor the best way to do this, but it works perfectly for what I need.

IBOutlet link to embedded container view controller

I have a complex iPad view that I manage by having several view controllers. I previously (before iOS6/Xcode 4.5) did this by allocating my view controllers in code, and hooked up the various views to them though links to the master view.
What I would like to do is use the new UIContainerView container views to embed the view controllers in the storyboard file. I don't seem to be able to make an IBOutlet link to the embedded view controller to the master controller.
Is it possible to do this? Or to retrieve the embedded controller via a tag or something in the code?
This question is SPECIFICALLY about using container views
Another option for some cases is to capture the embedded controller using -prepareForSegue:sender:.
For example, if I have a UINavigationController embedded within a CustomContainerViewController, I can name the embed segue embedContentStack in the storyboard and capture it in CustomContainerViewController via
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"embedContentStack"]) {
// can't assign the view controller from an embed segue via the storyboard, so capture here
_contentStack = (UINavigationController *)segue.destinationViewController;
}
}
I'm not sure what you mean by "retrieve the embedded controller". When you want to use a controller you use the UIStoryboard method instantiateViewControllerWithIdentifier:, using the identifier that you give to the controller in IB. You can also use the performSegueWithIdentifier:sender: method (which also instantiated the view controller). You should check out the "Using View Controllers in Your App" section in the Apple docs. It also makes reference to the fact that child view controllers are instantiated at the same time as the container controller.
After edit: If you embed a container view in another view controller, that embedded view's controller can be referenced from the containing controller with self.childViewControllers (which will be an array, so if there is just one, you can get it with lastObject).
Here is another thread about it: Access Container View Controller from Parent iOS
They propose to keep a reference in prepareForSegue or search for the embedded viewController in self.childViewControllers
Note of Caution
Before proceeding to use an answer to this question, you may wish to reflect whether the embedded things really need to be view controllers.
Eg, if you're embedding a UICollectionViewController subclass, could you instead embed a UICollectionView subclass? Or, even better, could you embed a UIView subclass that hides away the UICollectionView behind a simple ViewModel?
In the code base I'm currently working on, I'm embedding two view controllers in to another view controller. Both could fairly easily be plain views instead, and could then be more easily bound to in the storyboard, without this messy code.
Unfortunately, they are currently view controllers and I'm not in a position to simplify them in to plain views right now, so this will have to do.
Background
I'm using the approach of picking up the embed segue in prepare(for segue:, sender:) as suggested by Playful Geek here.
I wanted to show the swift I'm using for this, as it seems to be fairly tidy…
class EditionLandingViewController: UIViewController {
fileprivate var titlesView: SectionTitlesViewController!
fileprivate var sectionsView: SectionsViewController!
}
//MARK:-
extension EditionLandingViewController {
private enum SegueId: String {
case embedTitles
case embedSections
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard
let segueRawId = segue.identifier,
let segueId = SegueId(rawValue: segueRawId)
else { return }
switch segueId {
case .embedTitles:
self.titlesView = segue.destination as! SectionTitlesViewController
case .embedSections:
self.sectionsView = segue.destination as! SectionsViewController
}
}
}
Discussion
I've chosen to name segues as action methods.
Using an enum cases for segue identifiers means you've got the compiler and tooling on your side, so its much harder to get a segue name wrong.
Keeping the segue ids in a private enum within the extension scope seems appropriate in this case as these segues are not needed anywhere else (they can't be performed, for example).
I'm using implicitly unwrapped types for the embedded view controllers because (in my case anyway) it's a logic error if they are missing.
Similarly, I'm also happy to force cast the destination view controller types. Again, it would be a logic error if these types are not the same.
Swift version of the top-voted Answer. Years later, Interface Builder still does not seem to support dragging IBOutlets to embedded Container Views.
Instead, set the outlets in prepare(for:sender:):
#IBOutlet var someViewController: SomeViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedSomeViewController", let destination = segue.destination as? SomeViewController {
someViewController = destination
}
}
You must also set up the UIContainerView on your Storyboard. Xcode will generate an embed segue automatically; set the segue's Identifier.

Resources