Why my value is not passing into secondViewController [closed] - ios

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I want to pass simple String from oneVC to secondVC. I'm new to iOS.
I declared String nameOfFilm.
When I'm clicking on CollectionViewItem I'm downloading JSON, taking one value of it(String) and assign it to nameOfFilm. Then I'm starting segue to the secondVC.
in the PrepareForSegue method with proper identifier I compare the secondVC value to the nameOfFlim.(nextScene.name = nameOfFilm)
In the result, when the secondVC comes in, nextScene.name is empty.enter image description here
JSON i parsed properly. I think it may be mismatch in time and some functions are too fast. What is causing the problem?
var nameOfFilm = String()
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
INDEX_NUMBER_BEFORE = indexPath.row
let filmID = arrayWithID[INDEX_NUMBER_BEFORE]
downloadFilm(url: "https://api.themoviedb.org/3/movie/\(filmID)\(key)&append_to_response=videos,images")
performSegue(withIdentifier: "toTheDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toTheDetail" {
let nextScene = segue.destination as! FilmDetailViewController
nextScene.name = nameOfFilm
}
}

The issue here will be that downloadFilm will be an asynchronous task that occurs in the background and takes some time to make the request and respond with the value that you need.
Your downloadFilm function should accept a callback so that you can wait for the response value and then perform an action based on that response, a typical way of using this would be something like:
downloadFilm(url: "https://api.themoviedb.org/3/movie/\(filmID)\(key)&append_to_response=videos,images") { filmName in
self.nameOfFilm = filmName
self.performSegue(withIdentifier: "toTheDetail", sender: self)
}
To do this you would need to update your downloadFilm function, so that its similar to this...
func downloadFilm(url: String, completion: #escaping (_ filmName: String) -> Void) {
// do work here to setup network request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// parse response and get name
completion(name)
}
}
Here is a guide on Swift completion handlers that will help understand the concept.

Related

How can I transfer multiple rows of a tableView to another ViewController

I'm trying to add a feature in my app, to add multiple members to one action at one time. The members are listed in a tableView and the user can select multiple rows at one time with the .allowsMultipleSelection = true function. I got the following code but this doesn't work. I think my idea would work but not in the way I have it in the code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedRows = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
destination.members = members[selectedRows]
}
Does somebody out here know, how I can solve this problem, because there is an error :
Cannot subscript a value of type '[Member?]' with an index of type '[IndexPath]'
I have the same feature in the app but just for one member. There I in the let selectedRows line after the indexPathForSelectedRow a .row. Is there a similar function for indexPathsForSelectedRows ?
Or is this the wrong way to do it?
You need
destination.members = selectedRows.map{ members[$0.row] }
As the indexPathsForSelectedRows indicates, it returns an array of IndexPath. What you need to do is create an array of Member objects based on those path.
Assuming you have a "members" array that contain all the members the user can select from, and your table has only 1 section:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var selectedMembers: [Member] = []
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedIndexes = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
for selectedIndex in selectedIndexes {
let selectedMember = members[selectedIndex.row]
selectedMembers.append(selectedMember)
}
destination.members = selectedMembers
}
You can also use the array map() function to change the for loop into a single line operation:
let selectedMembers: [Member] = selectedRows.map{ members[$0.row] }
Both should effectively do the same.

Swift control flow

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
submitTapped()
if let scheduleController = segue.destination as? ScheduleController {
scheduleController.jsonObject = self.info
}
}
In submitTapped(), self.info is assigned a value. But when I run my app, self.info is reported as "nil". I tried setting breakpoints at each of the three lines, and it seems that submitTapped() doesn't execute until after this function is finished.
Why is this? Does it have to deal with threads? How can I get submitTapped() to execute before the rest? I'm just trying to move from one view controller to another while also sending self.info to the next view controller.
UPDATE:
I ended up figuring it out (for the most part) thanks to the answer below + my own testing.
#IBAction func submitTapped() {
update() { success in
if success {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showScheduler", sender: nil)
}
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// I'll probably check the segue identifier here once I have more "actions" implemented
let destinationVC = segue.destination as! ScheduleController
destinationVC.jsonObject = self.info
}
public func update(finished: #escaping (Bool) -> Void) {
...
self.info = jsonObject //get the data I need
finished(true)
...
}
The network request is an asynchronous task that occurs in the background and takes some time to complete. Your prepareForSegue method call will finish before the data comes back from the network.
You should look at using a completionHandler and also only triggering the segue once you have the data.
so your submitTapped function (probably best to rename this to update or something) will make the network request and then when it gets the data back will set the self.info property and then call performSegueWithIdentifier.
func update(completion: (Bool) -> Void) {
// setup your network request.
// perform network request, then you'll likely parse some JSON
// once you get the response and parsed the data call completion
completion(true)
}
update() { success in
// this will run when the network response is received and parsed.
if success {
self.performSegueWithIdentifier("showSchedular")
}
}
UPDATE:
Closures, Completion handlers an asynchronous tasks can be very difficult to understand at first. I would highly recommend looking at this free course which is where I learnt how to do it in Swift but it takes some time.
This video tutorial may teach you basics quicker.

iOS Swift: best way to pass data to other VCs after query is completed

Context: iOS App written in Swift 3 powered by Firebase 3.0
Challenge: On my app, the user's currentScore is stored on Firebase. This user can complete/un-complete tasks (that will increase/decrease its currentScore) from several ViewControllers.
Overview of the App's architecture:
ProfileVC - where I fetch the currentUser's data from Firebase & display the currentScore.
⎿ ContainerView
⎿ CollectionViewController - users can update their score from here
⎿ DetailsVC - (when users tap on a collectionView cell) - again users can update their score from here.
Question: I need to pass the currentScore to the VCs where the score can be updated. I thought about using prepare(forSegue) in cascade but this doesn't work since it passes "nil" before the query on ProfileVC is finished.
I want to avoid having a global variable as I've been told it's bad practice.
Any thoughts would be much appreciated.
Why don't you create a function that will pull all data before you do anything else.
So in ViewDidLoad call...
pullFirebaseDataASYNC()
Function will look like below...
typealias CompletionHandler = (_ success: Bool) -> Void
func pullFirebaseDataASYNC() {
self.pullFirebaseDataFunction() { (success) -> Void in
if success {
// Perform all other functions and carry on as normal...
Firebase function may look like...
func pullFirebaseDataFunction(completionHandler: #escaping CompletionHandler) {
let refUserData = DBProvider.instance.userDataRef
refUserData.observeSingleEvent(of: .value, with: { snapshot in
if let dictionary = snapshot.value as? [String: AnyObject] {
self.userCurrentScore = dictionary["UserScore"] as! Int
completionHandler(true)
}
})
}
Then when you segue the information across...
In ProfileVC
Create 2 properties
var containerVC: ContainerVC!
var userCurrentScore = Int()
Include the below function in ProfileVC...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ProfileToContainerSegue" {
let destination = segue.destination as! ContainerVC
containerVC = destination
containerVC.userCurrentScore = userCurrentScore
}
}
In ContainerVC create a property
var userCurrentScore = Int()
Ways to improve could be an error message to make sure all the information is pulled from Firebase before the user can continue...
Then the information can be segued across the same way as above.
Try instantiation, first embed a navigation controller to your first storyboard, and then give a storyboardID to the VC you are going to show.
let feedVCScene = self.navigationController?.storyboard?.instantiateViewController(withIdentifier: "ViewControllerVC_ID") as! ViewController
feedVCScene.scoreToChange = current_Score // scoreToChange is your local variable in the class
// current_Score is the current score of the user.
self.navigationController?.pushViewController(feedVCScene, animated: true)
PS:- The reason why instantiation is much healthier than a modal segue storyboard transfer , it nearly removes the memory leaks that you have while navigating to and fro, also avoid's stacking of the VC's.

Populating prepareForSegue's destinationViewController via returned type

Good afternoon!
I'm relatively new to Swift, though I think I've managed to wrap my head around most of it, however I'm having difficulties setting a segue's destinationViewController indirectly.
I understand that destinationViewController accepts AnyObject? but how do I go about returning the class as a function's return value directly to destinationViewController? Something like:
override func prepareForSegue( segue: UIStoryboardSegue, sender: AnyObject? ) {
if( segue.identifier == "nextView" ) {
let nextScene = segue.destinationViewController as? getNextView()
// ...blah blah blah...
}
}
Where getNextView() is overridden by a subclass whose sole purpose is to return a reference to the destinationViewController:
override func getNextView -> AnyObject! {
return SomeClassBasedOnUIViewController
}
XCode isn't happy I'm employing "consecutive statements" on one line in my prepareForSegue() and I'm at a loss how to resolve it so any help would be greatly appreciated!
Thanks!
EDIT:
Sorry about that, but getNextView() doesn't actually return an Optional. Just picked up on that and amended the question. The issue persists regardless, though.
What you're trying to do is illogical. This is a compile time indication of the class or protocol to which the reference must conform. Trying to get this reference at runtime won't help you. Instead you should be checking for conformance to a static protocol and then dispatching calls based on that protocol to an unknown implementation class.
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject!) {
if segue.identifier == "nextView" {
let nextScene : BaseClassBasedOnUIViewController = segue.destinationViewController as! getNextView()
}
}
destinationViewController shouldn't be optional. Can you try this one.
override func getNextView -> AnyObject! {
return SomeClassBasedOnUIViewController
}

Playing recorded audio with swift

I am currently working through the "Intro to iOS App Development with Swift" course through Udacity. I have copied the code exactly up to the point where we finish recording the audio. This is what they tell you to type:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if (segue.identifier == "stopRecording"){
let playVC:PlayViewController = segue.destinationViewController as PlayViewController
let data = sender as RecordedAudio
playVC.receivedAudio = data } }
However, it returns a compiler error and asks me to add the exclamation point after both as. When I run the program, it says "I found nil while unwrapping an optional". I'm relatively new to programming, so any advice would be helpful.
I just completed this course. So basicly what you are tring to do is passing data between differnet screens. Try to understand what are you tring to achive and it help you to understand the code better.
The main task for the first screen is to record audio, after the task completed, you store all the information about the completed task into an object called RecordedAudio. The information included are var title: String! and var filePathURL: NSURL!. After we store the recording info., we are ready to pass it to the next screen's controller, namely PlayScreenController. First, we have to get access to the conroller and than passing the data.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
// Checking segue if there are more than one
if (segue.identifier == "stopRecording") {
// Get the PlayScreenController access
let playSoundVC = segue.destinationViewController
as playScreenViewController
// Cast sender into a RecordedAudio Object
let data = sender as RecordedAudio
// Passing the data to PlayScreen
playSoundVC.audioData = data
}
}
Now everything is prepared, we can perform the segue. During perform the segue we have to provide the identity of segue and who are the sender.
var recordedAduio = RecordedAduio()
recordedAduio.filePathURL = recorder.url
recordedAudio.title = recorder.url.lastPathComponent
self.performSegueWithIdentifier("stopRecording", sender: recordedAudio)
Pay attention to the sender object in performSegueWithIdentifier that the reason why we can cast the send in prepareSegue into a RecordedAudio object.
NOTE: Remeber to define var audioData: RecordedAudio! in the PlayScreenViewController otherwise you cannot pass the data to the second screen, becuase there is no variable can hold the data which you are trying to pass.

Resources