How to pass variable reference between viewcontrollers? [duplicate] - ios

This question already has answers here:
How do you share data between view controllers and other objects in Swift?
(9 answers)
Closed 7 years ago.
I am writing an application with a customizable color palette. I have a settings class (settings) which stores these colors, and a settings viewcontroller (themeVC) which lets the user modify them, and another to list the possible values (colorsVC).
I keep a reference to the settings in both view controllers:
let settings = Settings.sharedInstance
In the themeVC I list categories like background, text and so on in a tableview like this:
let cell = tableView.dequeueReusableCellWithIdentifier("right detail with disclosure", forIndexPath: indexPath) as UITableViewCell
switch indexPath.row {
case 0:
cell.textLabel?.text = "Backgrounds"
cell.detailTextLabel?.text = settings.backgroundColor.name()
case 1:
....
default:
cell.textLabel?.text = "Texts"
cell.detailTextLabel?.text = settings.textColor.name()
}
return cell
I am trying to pass the settings value to the colorsVC, but this way it is just copied:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "showColorsViewController") {
let detailVC = segue.destinationViewController as! ColorsViewController
detailVC.setting = settings.backgroundColor
}
}
ColorsVC lets the user pick a color and then modifies the corresponding value in settings based on which row was selected in themeVC.
How can I pass a different value for every cell so the receiving colorsVC can read AND modify the corresponding setting?
I am looking for something like the inout keyword, but sadly I couldn't use it in this scenario.

Classes are reference types:
Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.
In the case above I think you would be best setting detailVC.setting to setting and then accessing/modifying the object properties on the object directly instead of passing the value of a class property, Does this help?
See Classes are Reference Types in the documentation
EDIT:
I believe you are using the colorsVC to set a color on the themeVC, so you segue to the colorsVC, select a color and this is returned to the themeVC table cell, correct?
If so there are a couple of ways to achieve this, I think the best way would be to pass the cell's row to the colorsVC so that it can update it as needed.

Related

How to create dynamic form in iOS?

I have to create one dynamic form which contains question answer set and questions are different for every user. There are 5 questions out of this user have to answer at least 3 questions. In the set of 5 questions, the first user may get the set question which required 1 textfield 2 drops down and 2 radio buttons in the same way the second user may get 3 textfield 1 drop down and 1 radio button. What approach I should have to follow to achieve this?
I tried to create a table view. In the cell, I specified one label to render a question and one blank view which will be filled later on the basis where the question required text field or radio button or drop down. But with this case, I am not able to maintain which question I get answered because if I make textfield or checkbox's user interaction unable the didselectrowatindexpath method is not getting called even tried to mapping with delegate but this one is also not suitable for me because I have so many cases to manage as an answer field.
There might be different implementations. What I would do is:-
Distinguish the Question Types based on the UI(as per your case)
Add a questionType enum in QuestionModel class
Will create a different cell for each question type
Use that particular cell for each question type
Even in future any new Qustion Type has come, add one more case in enum and create one more cell
My Implementation will go something like this for QuestionModel class:-
Class Question {
enum Type {
case type1, type2 .....
func cellIdentifier() -> String {
switch self {
case type1:
return "type1"
//Handle all cases
}
}
}
var type: Type
}
Controller class will be something like this:-
Class Controller: UIViewController {
var questions = [Question]()
//Table view delegate method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let questionAtIndexPath = questions[indexPath.row]
let cell = tableView.dequeReusableCell(withIdentifier: questionAtIndexPath.type.cellIdentifier(), for: indexPath)
cell.configureWith(questionAtIndexPath)
return cell
}
}
Well, the main task you have is to create a dynamic form where you have different objects depending on the required question. So, do this first:
Create individual tableCells like one containing a label for the question and a textfield for the answer or checkbox's to answer the question. The label will be common for every tableCell.
Connect the required delegates or IBOutlets with the objects
Create closure in table cells and handle the data when user completed answering the question.
Finally save the data collected by the closures call backs in TableViewcellforRowAtIndexpath and use them as per required.

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)

Data exchange between UIViewControllers, Service and Views

Top brick problem I face every time is a passing data between component.
I can separate this problem in few sub-problems:
Do we need to pass data to views? Do we need to change model directly in view?
Can we use some service call directly from views, or we need to pass back all operation to UIViewConrtoller and only then controller will request appropriate service.
Specifying indexes instead of real models.
So the first question demonstrate case when we create UITableViewCell and pass to it data directly. So we now have ability to modify some properties of this data object. Let's say we have PlayListViewController that implement UITableView datasource. If you see bellow I set data model directly into view.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = viewController.theTableView.dequeueReusableCellWithIdentifier(String(SongTableViewCell.self)) as! SongTableViewCell
cell.song = songs[indexPath.row]
return cell
}
Second one for example, when you want to trigger something in your service directly from your SongTableViewCell without using any delegate calls. Let's say in SongTableViewCell I want to play my song and on this cell I have play button. So then it is a simple solution - I can bind cell with UIButton action touch up inside for example and invoke needed operation in my service:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
MusicService.playSong(song)
}
But usually I do another things. I store delegate instance in my cell that pass back any action to a controller and controller decides what to do:
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton()
}
In this case SongTableViewCell looks like this:
class SongTableViewCell: UITabeViewCell
{
weak var delegate:SongTableViewCellDelegate?
...
#IBAction func onTappedPlayButton(sender: AnyObject)
{
delegate?.didTappedPlayButton(index)
}
}
and my view controller implements this didTappedPlayButton method where it calls MusicService.playSong(song). Here is 3rd problem if we have not pushed model object into UITableviewCell then we need to say somehow to view controller that it needs to play some appropriate song from array. So I use index that I set into the UITableviewCell which is sometimes can tangle other developers. I don't know if it's better to use index or data model. I understood advantage of changeability but index say nothing for developers and data model object says a lot.
I know it's more architecture questions, but maybe we can outline some props and cons of these 3 approaches/problems.
I found a post and it says
Classes=more complexity. Values=less complexity. Not sure it can fit all my subquestion, but maybe values it much better then transit entire class.

Change variable based on Table View Cell

I need an event, that changes a variable based on which TableViewCell I click. But unlike an action connected to a button, there is no action indicator for table view cells at all. So my question is:
I want to make a TableView that contains items of an array. Based on which item I click, I want to change my variable so that the result on the next ViewController depends on which button you click.
So to make things easier, here is an example what I want the app to look like:
On the first TableViewController I have a list based on an array and on the second ViewController I have a label that shows text based on the variable.
I have a nameArray = ["Mum", "Brother", "Me"] and a weightArray = [140, 160, 120] and a variable weight = 0. The label on the second ViewController tells the var weight. So when you click on "Mum" in the TableView I want the next ViewController to say 140, when I click on "Brother" then 160 and so on...
Until here everything works just fine and I have no problems with anything but changing the var based on what I click.
Long story, short sense:
I want an Action for the TableViewCell that changes the var like in an Action connected to a Button, but there is no Action outlet for Cells at all.
Use this method. Use indexPath.row to find what row number you selected
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var cell : UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
switch cell.labelSubView.text as! String {
case "Mum":
self.weight = weightArray[0]
case "Brother"
self.weight = weightArray[1]
and so on..
..
default:
statements
}
}
Note A better alternative
I also considered a case where you have too many entries in nameArray and switch statement might not be good. In that case you can get the text inside the selected row by cell.labelSubView.text as! String
next you can check if the nameArray contains the cell text and get the index of the name that matches the cell text. Next you can get the required weight at the same index in weightArray. And then do self.weight = weightArray[requiredIndex]
Hope this helps.
Update : My experienced friend #Duncan mentioned down below that switch statement in this case is a bad coding practice . I am not going to delete it because it is a lesson for me and also my fellow programmers who are relatively new to programming. So i have put it in a yellow box, stating that it is not a good code
A better option for this would be :
As Duncan mentions, creating an array of dictionary is a good option
Second option is the option in my answer after my Note
You need to maintain array of dictionaries , those dictionaries have keys like "person", and "weight", then you can easily get weight value after selecting the cell by using table view delegate method UITableViewDelegate's tableView:didSelectRowAtIndexPath:
Create an instance variable in your view controller (a var at the top level after the class definition) for the selected cell.
class MyTableViewController: UIViewController
var selectedRow: Int
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath)
{
selectedRow = indexPath.row
//invoke a segue if desired
performSegueWithIdentifier("someSegue");
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "someSegue"
{
//Cast the destination view controller to the appropriate class
let destVC = DestVCClass(segue.destinationViewController)
destVC.selectedRow = selectedRow
}
}
As Andey says in his answer, it's probably better to create a single array of data objects (dictionaries, structs, or custom data objects). Then when the user taps a cell, instead of passing the index of the selected row to the next view controller, you could pass the whole data object to the destination view controller. Then the destination view controller could extract whatever data it needed. (Weight, in your example.)

Working with Multiple Segues | How to use Cell data to perform specific Segues

I am displaying data in a collection view, I know how to pass the data on with prepareForSegue function but am trying to have the app determine which segue to use depending on the cell property data. (Each segue goes to a different view controller to display relevant information.)
For e.g.
If the cell.type is equal to "1" then perform segueOne if it is of type "2" then perform segueTwo.
I was trying to do something like this;
func collectionView(collectionView: UICollectionView, shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CollectionViewCell
if cell[indexPath].type = "1" {
performSegueWithIdentifier("showPage1", sender: self)
} else if self.cell[indexPath].type = "2" {
performSegueWithIdentifier("showPage2", sender: self)
} else { println("error when selecting cell to segue") }
}
However with this I get an error;
'CollectionViewCell' does not have a member named Subscript
Has anybody got any ideas ?
Assuming the items in your collection view can be re-arranged (or might be some time in the future), the indexPath will not be sufficient to give you the information which cell was selected. Thus, IMO your idea to give the cell a property is a feasible one.
The easiest "quick and dirty" way is to simply hardcode the segue identifier string into your cell. This is not the best design because you are introducing dependencies between app elements that should know of each other.
class MyCell : UICollectionViewCell {
var segue = "DefaultSegue"
}
Now calling the appropriate segue is really easy in didSelectItemAtIndexPath...
self.performSegueWithIdentifier(cell.segue, sender:cell)
It would of course be preferable to use an enum. Safer, more readable and better maintainable.
enum Segue : String {
case ToInfo = "SegueToInfo"
case ToLogin = "SegueToLogin"
// etc.
}
The ivar for MyCell would now be var : Segue = SomeDefaultValue and you can call it the same way.
BTW: Regarding your original question please note the following: as has been pointed out, you cannot subscript a cell. (UICollectionViewCell is not a Dictionary, so cell["key"] does not make sense.) Also, I am not a fan of dequeueing the cell in more than one place - instead you could call cellForItemAtIndexPath or do the work in that method in the first place, as I have suggested.
You're trying to index into a UICollectionViewCell, but of course that class is not an array, so you can't 'subscript' it.
My suggestion is to refactor your code. Whatever data you're storing in your cell you can presumably get from your data model, because that's where it originally came from. You are probably putting that in your cell in cellForIndexPath.
If that is the case, then there is no reason you can't get the same data from the same place in your func ... shouldSelectItemAtIndexPath ... -> Bool. I'd suggest doing it there. Your cell should only contain the data it needs to properly render itself to the screen.
See if that helps.

Resources