Reload same View Controller but with different information - ios

I have a Detail View Controller and I want to show the same Detail View Controller but with a different item.
var nexttype : Type!
let nexttypeVC = TypeDetailVC()
#IBAction func buttonTapped(_ sender: UITapGestureRecognizer) {
nexttype = type.Array![0]
nexttypeVC.type = next.type
self.navigationController?.pushViewController(nexttypeVC, animated: true)
}
When I print the name or a identifier of the item I get the correct one, but when I launch the simulator I get error. The values of the labels and everything is nil.
How can I tell the view controller to reload again but with a different item?

Call performSegue(withIdentifier:) rather than pushViewController. You'll need to programmatically register a segue or add one via storyboard. Then use prepare(for segue: sender:) to give the destination ViewController the item you wish. Note that the destination's viewDidLoad() is called after the prepareForSegue.
This is the standard Apple-recommended way to seed the destination ViewController with data it needs prior to its load.

Instead of pushing the same view controller onto the navigation stack. Try to arrange all the logic to update UI elements into a method. Call this method in viewDidLoad(). Calling self.viewDidLoad() is not a good idea.
Use observer pattern and call the method that updates UI elements.

Related

Swift 3: How to make sure that data is set before presenting in next view controller

I am using prepare for segue to set a dictionary variable in another view controller. But the issue is the view controller opens the map without the data. How can I make sure I go to next view controller only after the dictionary variable data has been set.
Here is my code
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showMapSegue"
{
if let mapVC = segue.destination as? MapVC
{
if vendorStoreKeys.count != 0{
mapVC.vendorStore = vendorStoreKeys
}
}
}
}
But the vendor store array in next view controller is still empty when the controller is visible. It sets the data after some time but at that point I am unable to reload that view controller so I need to make sure I don't go to next view controller before the mapVC.vendorStore variable is properly set.
I was able to resolve the issue. Prepare for should have set the data before calling and it was. Issue was I actually forgot to remove the segue in the storyboard which was directly going from the button to the new view controller. So here is what I did
Removed the segue which was going directly from the collection view cell button to the next story board
Created a new segue from the top of the view controller to the next view controller
Named that segue "showMapSegue"
Build and Run. I got the data successfully in the next view controller. Hope it helps.

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.

IOS - I want to recycle the server data on Viewcontroller

When we move the a segue from A view to B view or from b view to A view, viewdidload get data from server all the time
So, can we keep the data that viewdidload get at first excute, without starting viewdidload each page?
It's waste of time to get data all the time, when we open each page.
From now we using pageviewcontroller, I think it is inappropriate it.
I using swift language.
If you have good idea, please let me know.
There are very many tutorials on the internet that show how to do what you want. Try a google search on swift pass data between view controllers.
At its very simplest, the second controller needs to have one or properties that can hold data from the first controller. The first controller must implement the following function:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
let secondVC = segue.destinationViewController as! ViewControllerClass
// Pass data to the second view controller.
secondVC.dataFromServer = dataFromServer
secondVC.otherData = otherData // etc.
}
The data will then be passed from the first controller to the second.

Pass data between three viewController, all in navigationController, popToRootView

The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.

Pass data to view controller before initializer when using segues. (Swift)

I'm new to swift and am trying to write an app with it.
I have a UIViewController that I am transitioning to. I have designed the UI in interface builder and I intend to use segues to manage the transition. However, the view controller relies on data that is passed into the view controller from the previous view controller.
If I have properties on my view controller then I will need to redefine my init method. But I wouldn't normally call the init method; it would be called for me before prepareForSegue. So I see a few possible solutions:
Make my variables optional (so I can pass them in prepareForSegue
and update the view then).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController: EventViewController = segue.destinationViewController as! EventViewController
viewController.event = self.event
}
Manually init my view controller and present it programmatically instead.
???
Is there a third option? If not, which of the previously mentioned 2 is better practice?
There is two possible options as you mentioned:
The first one is the easiest which is to pass the data in prepareForSegue. which you don't have to care about dismissing the controller or keeping a track of inner view controllers,because storyboard will take care of it.
The second way is to set a Storyboard ID in storyboard,for the controller you need to present programmatically, which need more things to handle, like to dismiss the controller or keep track of inner presented controllers.
let nextViewControllerName = storyboard?.instantiateViewControllerWithIdentifier("Storyboard ID") as! nextViewControllerName
nextViewControllerName.event = self.event
self.presentViewController(nextViewControllerName, animated: true, completion: nil).
At the end they does the same purpose.
Note: You should always pass the data before presenting the controller.

Resources