iOS cycling between view controllers in a navigation stack - ios

I have been tasked with creating a flow in an iOS application where a user can add multiple steps, where the number of steps are undefined, each step acting as a ViewController within a navigation stack and the user can add multiple steps (VCs) while moving backwards in the navigation stack to edit, while moving back forwards to an existing step and not losing any inputted data.
Example: User creates Step 1, User creates step 2, user creates Step 3, user moves back to Step 2, User moves back to Step 1, Edits Info, moves forward to Step 2, moves forward to Step 3, etc.
So far I'm thinking of creating a sort of counter to keep track of what Step the user is on in addition to an array of Classes that hold the data that builds each VC but I'm having a little trouble with VC initializers and navigationController pushing and popping.
Any help would be appreciated, maybe someone has something up their sleeve.

You should create a class to hold steps objects
Something like this
class DataClass {
static let shared = DataClass()
var arrayObjects: [Any]
private init() {
arrayObjects = []
}
func addObject(object: Any) -> [Any] {
arrayObjects.append(object)
return arrayObjects
}
func clearObjects() {
arrayObjects.removeAll()
}
func object(at step: Int) -> Any? {
guard arrayObjects.count > step else {
return nil
}
return DataClass.shared.arrayObjects[step]
}
}
And use data something like this in each step.
DataClass.shared.object(at: step)

You can achieved above requirement by storing your ViewController locally into array.
Step:1 Create global Array of UIViewController as below.
var aryAllViewController = [UIViewController]()
Step:2 Append value into Array as below.
aryAllViewController.append(VC)
Step:3 Get old ViewController reference from Array and Push it into Navigation Stack again.
If you follow above step properly then old data will display automatically.

Related

Load UITable Data from another View Controller

I have two Views:
UITableViewController (View A)
UIViewController (View B)
I was wondering, if it's possible to load and setup the table from View B and then segue to View A, when the loading is done. I need this, since the Table View loads Data from Core Data and that takes some time; I would then show a Loading Animation or something. I have a function called loadData() in View A, which fetches all Elements from Core Data and then calls tableView.reloadData().
Does anyone know, how I could implement this? Or should I somehow show the loading View directly from View A with a SubView or something?
Remember to not think about the specifics but instead, think generally:
You want to move from one VC to another and you have some data that needs to be fetched asynchronically. Let's assume you can't know how long it will take.
My suggestion is to contain all data fetching related to a VC inside that VC itself (or services/facades related to it). So basically you should present the UITableViewController and then have it fetch the data while showing skeleton-cells/spinner/etc.
You want to have separation of concerns which means you don't want your ViewController to handle data related to another view controller.
Think about the following use-case: if you have code to fetch data in the previous VC, before presenting the TVC, what happens when you need to re-fetch the data or refresh something? You will have to duplicate the code in both the VC and the TVC.
That's why it's suggested to keep data fetching inside the view controller that needs it.
If, for some reason, you still want to have your answer for this specific question:
You can have the initial VC create the TVC, but not present it yet, call its methods to fetch the data, and have it send a callback (closure/delegate/etc) when it's done fetching. When the fetching is done, present the TVC.
Here is a quick example:
class MyTableVC: UITableViewController {
private var myData: [Int] = []
public func fetchData(completion: () -> Void) {
//Fetch data asyncly
myData = [1, 2 ,3]
completion()
}
}
class MyVC: ViewController {
private func loadTableVC() {
let tableVC = MyTableVC()
tableVC.fetchData { [weak self] in
self?.present(tableVC, animated: true, completion: nil)
}
}
}
Again, I wouldn't use this due to having tight coupling between the 2 view controllers, but it's always up to you to decide how to design your code.

Xcode 10, Swift 4 - How do I transfer data across multiple view controllers? [duplicate]

This question already has answers here:
Passing data between view controllers
(45 answers)
Closed 3 years ago.
I am currently working on an app and have problems with posting events.
I collect data in a sequence of several views (7 screens) and would like to store the data finally at once (or show all the data in the final view).
These data are, for example, location info, user info, event image, comments, event category, ...
I know how to store the data in the database/storage (firebase) if I collect the data in one or two views.
But in my use case I have seven views and I could not find any elegant method.
What's the best way to do that with Xcode 10?
You can use struct as below code. Make all required variable for all screen in this struct (like string, image etc..). And you can access this from any ViewController.
struct InputDetails {
static var details: InputDetails = InputDetails()
var city: String = ""
var lat: String = ""
var long: String = ""
}
Now to add value in this
InputDetails.details.city = textfiels.text
Now to access first screen value in last screen
print(InputDetails.details.city)
And once your API call or above struct usage is over, make sure to reset all details like below.
InputDetails.details = InputDetails()
There are several ways for passing data between View Controllers. For example you could use an instance property or a segue or the delegation method.
I recommend you study this article which paints a complete picture of the different methods and how to apply them:
How To: Pass Data Between View Controllers In Swift
Edit:
Upon examining the picture in your question I figured that using a segue would be the most appropriate solution here. As it seems from the picture you enter data in one View Controller, pass that onto the second View Controller and finally you upload all the data to Firebase.
I assume that you use storyboards (if not then consult the link above for other methods.) In this example below you will pass a string from one VC to another.
Step 1:
Add a segue between two view controllers. Storyboard -> press ctrl and click on VC one and drag your mouse -> you will see a blue arrow, drag that to VC two and release -> select manual segue: show -> click on the segue and give it an identifier
Step 2:
In VC two, make a string variable:
class SecondViewController: UIViewController {
var stringToPass: String = ""
override func viewDidLoad() {
super.viewDidLoad()
print(stringToPass)
}
Step 3:
In VC one, enter the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? SecondViewController {
vc.stringToPass = "This is the string we pass between two VC"
}
}
Step 4:
Then whenever you want to go to the SecondViewController perform the segue like this:
performSegue(withIdentifier: "identifierYouEntered", sender: self)

Struct Array in secondVC not being appended from the firstVC

I have created a struct and made an array of that type. The struct consists of two variable:
struct notesarray
{
var prioritycolor : UIColor
var note : String
}
In my secondVC which houses a collectionViewController, I have made an array of type notesarray. I am sending values for prioritycolor and note from firstVC.
I will be setting up CoreData later on, for now I just want this to work in simplest of manners. I am appending data from firstVC to this array like so:
#objc func handleCheckButton()
{
print("Added")
let secondVC = AddedNotesCollectionViewController()
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
print(secondVC.allnotes.count)
taskTextView.text = nil
}
allnotes is the name of the array found in secondVC.
For testing purposes I am printing secondVC.allnotes.count but I am just getting '1' in console no matter how many time I add elements to the array.
I have also tested this by placing print(allnotes.count) under viewDidAppear func in secondVC so that whenever I go to secondVC it gives me count of the elements in the array but it also shows '0' every time.
I don't know what I am doing wrong here. Please help me!
Thats because you end up getting a new instance of AddedNotesCollectionViewController every time you press the button.
let secondVC = AddedNotesCollectionViewController()
And new instance is initiated with an empty array and you add one element to it by calling
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
Hence count is always one. iOS is correct there my friend :)
What you need:
If second VC is already loaded either by pushing a it on to navigation stack of FirstVC or if its presented then get the reference to the presented/pushed VC rather than creating a new one every time. There are many answers in SO which explains how to access the pushed/modally presented VC :)
If you are about to present/push the SecondVC, as you mentioned in the comments you can always make use of prepareForSegue to pass the data.
If in case your AddedNotesCollectionViewController is never presented then rather consider creating singleton instance of notesArray which you will share between multiple VCs.
Hope it helps

How to use an array over multiple view controllers?

I have been playing around with a lot of stuff involving arrays and scrollviews. I have mostly stayed within the confines of view controllers, so usually i'll grab data from firebase, add it to an array, and then send it to the tableview or collectionview. What I'm trying to do now is actually navigate between viewcontrollers (or multiple copies of the same view controller) and applying the array items to each view controller.
For example I want to be able to grab some photos from firebase, put them in an array of url strings or whatever. Then I want to put a photo on the background of a view controller. Then when I push the over button it goes navigates to the next view controller and puts the next photo as the background there, etc.
I understand there are probably multiple ways to do this and I was wondering what is the most efficient way? Do I just put an array in a Global class and access that from all the view controllers? Or do I just keep the array in the first view controller, then as I navigate, keep sending it to the next view controller over and over? Also there will be a LOT of items and objects and arrays here so that's why I'm looking for efficiency. Thanks in advance to anyone able to help with this, and I hope I explained it well enough!
This is a very simple way of adding and retrieving String value from a struct, here you are saving the image url string as a value in a dictionary and it's key is going to be the ViewController name.
struct SavedData {
static private var imagesDictionary: [String: String] = [:]
static func image(for viewController: UIViewController) -> String? {
return imagesDictionary["\(type(of: viewController))"]
}
static func add(image name: String, for viewController: UIViewController) {
self.imagesDictionary["\(type(of: viewController))"] = name
}
}
saving a value is very simple, if you're saving the data in a viewController and you want a specific image to be saved for that viewController you can use self
SavedData.add(image: "img1.png", for: self)
And if you want to save an image for a different viewController, do it like this.
SavedData.add(image: "img2.png", for: SecondViewController())
Retrieving the image is also very simple, you should call this method in the viewController that you want to assign the image to.
let savedImage = SavedData.image(for: self)
print(savedImage!)

Passing selected rows between ViewControllers and conditional segues in Swift

I'm working on my first app and I've got the UI sketched out, but before I move forward, I've hit a stumbling block out of the gate that's not in the tutorials I've been studying.
Two issues I have:
1) Passing data between TableViewControllers
2) Conditional Segues
The app uses CoreData and has a context with 3 managed objects in the model: FocusArea, Equipment, & Activity.
A portion of my app will have 3 TableViews that display fetched results from FocusArea, Equipment, & Activity. The navigation through them will be as follows:
Step 1 -> Step 2 -> Step 3 -> save selections
focusAreaTVC -> equipmentTVC -> activityTVC -> saveVC
equipmentTVC -> focusAreaTVC -> activityTVC -> saveVC
activityTVC -> focusAreaTVC -> equipmentTVC -> saveVC
When an item/items is/are selected in Step 1, a "next" button will advance to Step 2, display options available for rows selected in Step 1. Once the user makes selections in Step 2, the remaining available selections will be displayed in Step 3. I envision the final selections on the Save screen to be saved as an array or a dictionary.
Issues:
1) View Controllers: Rather than use 9 different ViewControllers, I plan to use performSegueWithIdentifier so I only need to set up 3 view controllers for the objects and figure out the logic to accomplish the transitions.
Would I create a global variable in AppDelegate for the TVC at each step and put logic in a switch statement for each VC within performSequeWithIdentifier?
example:
// AppDelegate variables start all nil, updated at each step, reset to nil on save
var step1VC = "equipmentVCUsed"
var step2VC = "focusAreaTVCUsed"
var step3VC = nil
// VC switch statement (contained in each TableViewController)
var segueIdentifier: String
switch segueName {
case step1TVC = "focusAreaTVC":
segueName = "equipmentTVC"
case step1TVC = "equipmentTVC:
segueName = "focusAreaTVC"
case step1TVC = "activityTVC":
segueName = "focusAreaTVC"
case step2TVC = "equipmentTVC":
segueName = "activityTVC"
case step2TVC = "focusAreaTVC", step1VC = "equipmentVC":
segueName = "activityTVC"
case step2TVC = "focusAreaTVC", step1VC = "activityVC":
segueName = "equipmentTVC"
case step3VC != nil,
segueName = "saveVC"
step1TVC = nil
step2TVC = nil
default:
println("Something so the the compiler doesn't yell at me")
}
2) I plan to make selections in Step 1 and pass them to Step 2's VC, further refine there, pass to Step 3. Since they're all working off the same context, can I just pass the selections from 1 TVC to another or do I need to create a list that the next VC will retrieve items from?
How would I get the information from Step 1 to Step 3? Would I have to save the selections in an Array between segues and refine them with fetches in each VC?
Why not just let the segues be triggered by taps on a table view cell? In other words, make it like this: User selects option from first table view -> User then sees second table view and selects from the appropriate set of options -> User then sees third table view and selects from the appropriate set of options. You can always put a back button at the top so the user can go back and change his/her selections.
Your users might want, and expect, your app to behave like this.
Anyway, to answer your questions:
1) I suggest that you create a UIViewController superclass from which your subclasses inherit, then define a property in that class's .h file that can be set to an object that contains whatever data each view controller needs to pass to the next view controller. Then, use prepareForSegue to pass along the data that the next view controller will need to have.
2) You can pass the selections from 1 TVC to another (see #1)
Edit: If you need any clarification, or if you feel I am misunderstanding your question, please feel free to leave a comment. I got less than 5 hours of sleep last night.

Resources