I'm trying to figure out how to navigate around my app. But i'm a little lost.
I have a UIViewController that loads some data, then displays the data in a CollectionView. Then I have another UIViewController for the detailed view. I then trigger a segue to go to it, I pass the data etc.
self.performSegueWithIdentifier("detailViewSeque", sender: nil)
But the part i'm lost on is getting back to my main view, if I just trigger another segue then it loads all the data / view again. The data has already been loaded once, I really don't want to keep loading it.
I feel like I'm doing things wrong, that theres some super obvious way to handle this scenario.
Could someone point me in the right direction?
This is good situation to use an unwind segue (for more information: What are Unwind segues for and how do you use them?). Here's how to setup one up:
Firstly, create an #IBAction in the view controller you want to segue to, that takes a UIStoryboardSegue as its only argument. For example:
#IBAction func unwindToHere(segue: UIStoryboardSegue) {
// If you need you have access to the previous view controller
// through the segue object.
}
Secondly, you need to create the unwind segue in IB. To do this ctrl-drag from the view controller you want to segue from, to Exit and select the unwindToHere method:
Thirdly, you need to give your segue and identifier. To do this select your segue (see below - your segue will not be visible like normal segues); then use the Attribute Editor to give your segue an identifier.
Now you can use your segue. On the view controller you want to segue from, call:
self.performSegueWithIdentifier("YourID", sender: self)
To rephrase your needs "I have data that I need to keep around somewhere that isn't associated with a view controller".
You have a few options here. Your goal is basically to store it somewhere that isn't going to go out of memory.
The AppDelegate gets used for this purpose a lot but Singleton variable works as well.
I would personally create a singleton, say CatPictureRetriever with
private let _CatPictureRetriever SharedInstance = CatPictureRetriever()
class CatPictureRetriever {
static let sharedInstance = CatPictureRetriever()
var catPictures : NSArray?;
func gimmeCatPictures -> NSArray? {
return catPictures
}
}
Now you can get your pictures though your CatPictureRetriever anywhere
var pictures = CatPictureRetriever.sharedInstance.gimmeCatPictures()
Related
Upon app start, i have a login screen, which is an instance of LoginViewController and on success, i.e. after my user is successfully logged in, the main content of my app is being shown using the performSegue(withIdentifier identifier: String, sender: Any?) method to an instance of a UITabBarController. Unfortunately i deinit() is not being called on my LoginViewController after the segue. This does not happen due to any reference cycles i might have in my LoginViewController, because i explicitely checked for those.
Since a login screen is only required once upon app start, i was wondering if there is a best practice to be able to make sure that my LoginViewController is being deallocated after the segue?
Maybe using a segue is just not the right way to achive this, because
from what i understand, the conclusion of a similar question Deinit not called on Show Detail segue was the fact, that using a showDetail-segue is the same as using a modal presentation, i.e. both controllers keep a reference to each other and deinit() will not be called in either of them. Is this really ecpected behavior, or am i missing something?
Update: The reason i care so much, is the fact that i am using RxSwift and my LoginViewController is subscribed on several Observables, so after my app leaves the LoginViewController, i want to make sure to set my collected DisposeBag to nil. Would it be finde to do this in the ViewDidDisappear() of my LoginViewController?
I would suggest you to create a custom segue class and override the perform function like this:
class CustomSegue: UIStoryboardSegue {
override func perform() {
source.view.window?.rootViewController = destination
}
}
I've read several posts on this issue but none of them solved my problem.
I'm coding an app where I have to click on a button ("Prepare") to go to the following ViewController. When the button is clicked, it also passes data between the two view controller.
The problem is, when I click the button, the following ViewController loads twice. Thus, if I want to go back I have to go back through two same ViewController.
I've checked the segue, the class names and files names but nothing fixes it.
I've also created a new project and rewritten all the code from the beginning but in the end, it still doesn't work.
However, I've noticed that the problem showed up when I added the prepare(forSegue:) function and the performSegue function. Without it the ViewController only loads once. But of course, I can't get the data passed between the views without it...
Here is the screenshot of my two view and the code of the two functions :
First view
Second view
// Prepare the segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "prepareSegue" {
let timerViewController = segue.destination as! CountdownViewController
timerViewController.timeInterval1 = waitingTime
timerViewController.timeInterval2 = shootingTime
timerViewController.lastSeconds = lastSeconds
timerViewController.currentTimeInterval = waitingTime
}
}
// Prepare the timer when the button is pushed
#IBAction func prepareTimer(_ sender: Any) {
performSegue(withIdentifier: "prepareSegue", sender: self)
}
Probably, when two ViewControllers appear it's because you have:
A segue on Storyboard which start directly from a Button
IBAction attached to the button where you call a performSegue of the same Segue
To fix this problem, you need to create a Segue which start directly from ViewController. After you can use the IBAction and the performSegue
You have added a seague but also an IBAction. If the seague is defined well in InterfaceBuilder it will perform and call your method. The IBAction is the alternative way for connecting an Action to a button. If you use both, you have two actions.
Without the rest of your project to check, one thing it could be is a completion closure which contains the performSegue(...) statement is called twice, so the performSegue statement is literally run twice. How might this happen?
If you have a networking call inside the closure which contains performSegue command, and the networking call has a closure which calls completion(...) twice, then you could get either the segue occuring twice or even recursively!
It's rare this will be the case, or in your instance, but this type of problem can be uncovered by using lots of breakpoints and following the "bouncing ball" to see the completion() calls occuring more than once.
i'm fairly new to swift, so please bear with me.
right now my problem can be broken down to this:
I have one Test View Controller that can display my 'Test' object by showing the description and the title. I also have an array of Test objects. In the top right hand corner, there is a 'skip this test' button, and if the user clicks on it, the viewcontroller will segue to itself, but change the Test object that is being displayed, e.g. its just basically looping through an array of Tests. I have changed the prepare for segue methods accordingly to push the data through the view controllers.
However, I want to be able to move to a completely different ViewController (lets just call it FinalViewController) if I have reached the last Test in my Test array.
Now this is the part that I can't fix.
I would like to create a segue directly from the 'skip test' button, only that it segues to a different view controller, depending on a certain condition. However, as i tried creating a segue in IB by right clicking on the Button and pulling it to the FinalViewController, it erased my previous segue I had for that button.
Does anybody know if there is a fix for this problem? thank you!!!!
However, as i tried creating a segue in IB by right clicking on the
Button and pulling it to the FinalViewController, it erased my
previous segue I had for that button
Actually, you don't want to do that, instead, you should drag from the controller itself, not from the button because if your segue has been created based on a button, tapping on it should always perform it and this is NOT what you want.
After creating a segue from the ViewController, you can -programmatically- call performSegue method and handle what data should be passed to the next viewController by implementing prepareForSegue method.
For more information, check my answer to know how to create multiple segues with identifiers and work with them.
EDIT:
If you want to push to the same current ViewController, performSegue method would not be the optimal solution. You should instead push/present to same ViewController it self:
class ViewController: UIViewController {
var myString: String?
override func viewDidLoad() {
super.viewDidLoad()
// here we go
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sameViewController = storyboard.instantiateViewControllerWithIdentifier("ViewControllerStoryboardID") as! ViewController
sameViewController.myString = "the value of the new myString"
navigationController?.pushViewController(sameViewController, animated: true)
print(myString)
}
}
Probably, you don't want to implement it in viewDidLoad, it's just for demonstration purpose.
Make sure that the ViewController has a storyboard ID (in code snippet, I assume it is "ViewControllerStoryboardID"):
Evening, my question is full about theory.
I understood reading from Apple developer documentation that is better to use the Delegates Pattern to keep track of some object attributes. In this way we can access the delegate without access to the object. (I really didn't get the reason of this choice)
I also understood that is better to define: protocolDelegate: class
and when we are declaring the delegate inside the class it's better to use the weak word to prevent some "kind of problem cycle". (??)
So, while I was playing a bit with code, I've discovered that you can't pass a weak delegate between two view controllers, because of course, when you change the controller, the weak delegate is going to be deleted because is a weak thing (or at least this is what I understood).
So, I have to choose between 2 options:
make the delegate "strong" deleting the weak key.
or pass the object in the segue and keep the delegate as weak.
I have a lot of confusion, can you clear my mind? :D
The cycle you're referring to is called a retain cycle.
Let's use a concrete example to clear this up: say you've got a UIViewController which has a UITableView. The view controller has a strong reference to the table view. The view controller now wants to act as the delegate to the table view.
Now, if the table view would have a strong reference to its delegate, we would have the following situation: the view controller has a strong reference to the table view, and the table view in turn would have a strong reference back to the view controller. Thus neither can ever get deallocated.
To break this cycle, references to delegates are usually weak. This allows the retain count of the view controller to drop to 0 eventually, which can in turn release the table view.
Your classes that want to use delegates should also follow this pattern and use weak references to their delegates. You should thus pass the required references via your segue.
I will concentrate on the first part of your question, since the previous answers have covered the rest pretty well.
Consider the following situation: you have a class that handles some kind of network connection - it sends a request to a server and gets a response. Outside of this class there is a viewController that has a button that triggers the request and a view which presents the response to the user.
Basically, the network handling class should be able to get some message from the viewController (button pressed) on one hand and pass the viewController the response on the other. So there should be bidirectional communication between the two classes. While the passing of the buttonPressed message to the network handling class is pretty obvious, the reverse part (passing the response) is a bit more tricky, because the network handling class should not be aware of who created it and who calls it (good OO practices and memory leaks prevention).
That's the point where the delegate pattern comes in. It allows an object to pass data to whoever is interested in it without knowing anything about the recipient. The class that passes the response only knows some 'delegate' and not another class. In addition you can take out the network handling class as is and put it in another project. Because it isn't supposed to know any other class from its original project, only some 'delegate', it can be put into another project without any modifications.
I hope it can help you to get the reason of the choice.
I think pass the object with segue, Segues are a very important part of using Storyboards in Xcode. We can go over the different types of seguesanother time, but this one shows you how to use the “Show” segue, as well as how to pass data between the two view controllers to customize the second one with whatever custom data is required.
You can easily use segues example; Under below you can send currentstring to destinationViewController inside sentstring , also ShowSegue is your segue identifier
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowSegue" {
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.sentstring = currentstring
}
}
}
Navigation between viewcontrollers maintain stack of viewcontrollers.
For example aVC is firstviewcontroller then top of stack will be aVC,
now when you push or show another viewcontroller say bVC then now top of statck is bVC. So stack looks like,
aVC -> bVC(top)
now you push another cVC then,
aVC -> bVC -> cVC(top).
So top of stack is always visible to user.
at current situation, aVC and bVC and cVC are not deallocate. they are in memory. But if you pop or dismiss cVC, then it will deallocate from memory and now your top of stack looka like,
aVC -> bVC(top).
So viewcontrollers live in stack till they are not popped or removed. So, they are strog reference by default.
Segue is nothing but you can say that they are graphical representation of push or pop operation.
another thing is that delegate should be weak that because it can create retain cycle if they are strong.
you can called delegate as representative in general sense.
Now, if you are using segue, send your object in prepareForsegue and it will manage everything else.
I am struggling with this problem for a few hours now.
I have a view with a tableView and custom cell in it. What I am trying to do is to perform a segue which is in my viewController from the custom cell. The first thing I did was to create a variable called "parent" in my cell.swift with the type "viewController".
weak var parent : ViewController!
After that, I'm just trying to perform the segue like this :
parent.performSegueWithIdentifier("eventDetails", sender: parent)
And I have the following error : Receiver (<Deevent.ViewController: 0x7fb99074f5d0>) has no segue with identifier 'eventDetails'
So I tried something else... I created a button in my ViewController and connected a segue to the next view (everything from the storyboard file)
In my viewController.swift I created the following function
func viewEventDetails() {
performSegueWithIdentifier("eventDetails", sender: self)
}
and this one in my cell.swift :
func viewEvent(sender: AnyObject) {
parent.viewEventDetails()
}
So when I call it from the cell, it crashes with the same error than before but when I click on the button it's working. I even tried to click on the button programmatically btnDetails.sendActionsForControlEvents(UIControlEvents.TouchUpInside) and I had the exact same error.
I've already tried to clean my project and delete it from the simulator. I'm really missing something here... hope somebody can help me.
Thanks !
It sounds like you need to give your segue an Identifier.
A view controller can have multiple segues, and so you need to define which one you want to use. In your case, in your code you are using the Identifier "eventDetails".
Assuming you are using a Storyboard, you need to click on the segue arrow between the two view controllers, and in the Attributes Inspector on the right, set the Identifier value to "eventDetails".