Swift, How to use data passed back from VC in other functions? - swift

I'm having trouble accessing and using the variable the value I got from a popover
First time ever asking a question on here and fairly new to programming so please be gentle. My program has a popup that displays a date for the user to select, and then that date is passed back to the main view controller. I tested this aspect to make sure the data was being passed back to the vc by getting the program to display the date in a label using callbacks and delegation (I tested both ways), and it worked fine, but what I'm trying to do is use that value taken from the popup, placing it into a variable, and using it for further calculations in other functions.
I tried to take change the protocol function have a return value from originally (value: String) -> (), to (value: String) -> (String)
but then realized this wouldn't work when I try to access that function from say the viewDidLoad function I don't have an input value
like if I did this inside of viewDidLoad
date = popupValueSelected(value: ???) // throws an error because it doesn't know about the delegate
This is my delegate protocol code:
protocol PopupDelegate {
func popupValueSelected(value:String)
}
Here is the code used in my main view controller to get the Data from the popover, which all works fine
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let popup = segue.destination as! DatePopupViewController
popup.delegate = self
}
func popupValueSelected(value: String) {
date = value // how to access this value from somewhere else??
}
All I want to do is use the value that I from the popover in another separate function. How would I pass that value somewhere else if I can't return it? I've sifted through answers on here and saw someone ask a similar question, but the first answer I saw was to use delegation, but no further explanation. I don't want to display that value or use it right away, I only want to use it for another calculation, that will be displayed when the user clicks a different button.
I'm sorry if this doesn't make sense as I'm having a difficult time explaining the issue, hence the throwaway account, though I feel like there's something so simple that I'm missing.
I appreciate all the feedback I can get!

Unless I’m missing something you seem to be 90% of the way there. You’ve got popupValueSelected getting a value. If you store the value off to a class level variable you can then use it later. Let me know if I’m not understanding your issue fully.

Related

Swift: Referencing variables in one class from another function and/or class

trying a few things out in Swift. I’m trying to get some things that seem muddled to me straightened out - mostly to do with how I deal with variables and referencing them in a project.
What I am trying to do is keep a variable (based on a struct) defined in ViewController accessed and updated from various other functions within an application.
So, a brief outline of the code I have is here. I actually wrote a smaller app to test my ideas out before applying them to something more complex.
I started in XCode with a Swift document based application for Mac OSX.
In ViewController.swift I have:
import Cocoa
class ViewController: NSViewController {
var myText = "Hello, some text"
#IBOutlet weak var textView1: NSTextField!
#IBAction func Button1(_ sender: Any) {
myText = "This is button 1 clicked"
myText = setText( thisText: &myText )
textView1.stringValue = myText
}
#IBAction func Button2(_ sender: Any) {
print("Button 2")
myText = "This is button 2 clicked"
textView1.stringValue = myText
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
textView1.stringValue = myText
}
override func viewDidAppear() {
let document = self.view.window?.windowController?.document as! Document
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
In SetText.swift, I have this:
import Foundation
func setText( thisText: inout String) -> String {
thisText = "Function"
return thisText
}
I liked the idea of sending variables to the Set Text function and then returning it, but thinking about it more makes me think that actually this could end up like a proverbial bowl of spaghetti with functions calling functions and who know what else. So I was thinking that something like this would probably make more sense:
import Foundation
func setText( thisText: inout String) {
let vc = ViewController()
// Read the variable from View Controller
var myTextHere = vc.myText
myTextHere = myTextHere + " More text"
// Set the variable in ViewController here
vc.myText = myTextHere
}
From my reading around on this subject, if I call ViewController(), it will create anew instance of the view (is that right, or am I misreading?). That’s already happened, so what I need is to reference the ViewController that called the function setText, or rather that owns that particular instance of code. As I’m thinking about a document based application, I’d obviously want to keep all instances of myText with each document’s ViewController.
My aim is to create something a bit more complex, using a variable based on a Struct to keep everything together. So:
myCard.image // holds a bitmap image
myCard.size // holds the size of the image
And so on. being able to access it in the form of ViewController().myCard to both read and write to is what I am thinking I need to do.
What I don’t want to do is use global variables.
Thanks.
I'm having a hard time seeing much correlation between the title of your question and the body of your question. In fact, I'm not even entirely sure that there's a question being asked. That said, I'll try to address the questions you appear to be asking (re-written how I think they're intended):
When you initialize a new view controller, does it create a new view?
Yes. There is a view property for every view controller, and it's not a shared component or a singleton or anything else like that. There is one main view for every view controller. It's almost certainly composed of dozens of other subviews, but there is one view that is the view for every view controller.
Is there a way to get metadata about the state of a view controller from outside, preferably in the form of a struct?
Absolutely. First, you'd need to define that Card struct. I'd recommend doing it in the same file as the view controller itself. You can define it outside of the view controller, or if you want stricter coupling and namespacing, you can do it inside the view controller. Just be aware that doing that latter would mean that the type name, when referenced outside the view controller, would be ViewController.Card rather than just Card.
Then you'd want to create either a computed property (var card: Card) or a method (func card() -> Card) on your view controller which builds and returns one of those based on the state. It sounds like you're already leaning toward the property approach.
Note: I would absolutely advise against having one that is a normal get/set property because then you'd have to constantly update it and modify it. The best thing to do is have a computed property which builds it on the fly. So when the property is called, it reaches into all your components to get the info you want (like image sizes, strings, etc) and then packages up and hands off the Card metadata struct. Doing it on-demand like this eliminates unnecessary complexity and consolidates the metadata logic into one place.
Some dangerous things in your code example:
I can't think of a good reason to implement viewDidAppear() but not call super.viewDidAppear(). Unless you have an enormously compelling reason to leave that out (I honestly can't think of a single one), do not do so.
I don't see any good reason for your first implementation of your setText(thisText:) method. The way you use it in your Button1(_ sender: Any) IBAction functionally does absolutely nothing. That method in general is screwy for several reasons: it's got an upper-case method name, sets the textView text by trying to assign to stringValue for some super strange reason, and does in three lines what could be done in one:
textView1.text = "This is button 1 clicked"
The second implementation of setText(thisText:) makes even less sense than the previous. The two biggest problems being 1) that you don't even use the thisText argument passed in, and 2) your method, which is called 'set text' is creating a whole new view controller every single time it gets called? That's a huge violation of "doing what it says on the tin." Methods should have a single responsibility and shouldn't do anything beyond that responsibility. I'd never in a million years look at a method called setText and think "I'll bet this initializes a view controller." Never.
I see this question has already been downvoted a bit (not by me), and I'd like to take a moment to coach you in using Stack Overflow: Ask clear, concise, specific questions about clear, concise, specific topics. As I said at the top of my answer, there doesn't appear to be a question anywhere in your post. I had to kind of make some up that I inferred from what you wrote.
Remember: coding isn't just wiggling your fingers while you think about an app. If you're doing the hard work of good engineering, you'll likely spend a ratio of about 10:1 (or more!) of staring at your screen to actually typing any code. Every time you write a line of code, you should be asking yourself, "Why am I writing this line of code? Is this necessary? Am I reinventing the wheel?)
Good luck!

implement part of a github repository

I'm trying to modifyt this repository which I forked https://github.com/vinnytwice/BicycleSpeed
but without the use of the tableviews because i'm interested in display the data like speed cadence and distance inside a more graphically designed InfoSpeedoViewController.
I don't manage tableviews controller yet so I can't understand how and where to modify the project.
Can anybody help to point me in the right direction?
my version will have just two ViewControllers :
MainViewController with a ScanButton and SpeedoViewController
So far I deleted the InfoTableViewController.swift, that is just drawing the tableview. but I'm stuck in modifying MainViewController according to my needs. I don't understand the override function prepare(for segue ) part of the code. it passes data to the InfoTableViewController with the first if statement so I don't need it, but with the second if statement it starts the scan function bluetoothManager.startScan() and passes data to ScanViewcontroller via it's UINAvigationController and than to the tableView in the ScanViewController. Am I right?
If so, could I just call the scan function and pass data to my SpeedoViewController instead?
#IBAction func scanButton(_ sender: UIBarButtonItem) {
bluetoothManager.startscan()
performSegue(withIdentifier identifier: MainToSpeedoSegue, sender: self)
}
I don't really understand the relationship with scanViewController I guess. does data end to the infoTableView anyway?
thank
Finally found the solution. it was to change the tableViewController for a normal ViewController having labels to display values. Change the references from the tableViewController to the ViewController, and link the returned values to the labels inside the function that was receiving the continuously updated values.
Pretty easy once decoded the data flow.

How do set a relationship between managed objects within an MVVM pattern?

I'm building an app and trying to use the MVVM pattern. While there's a plethora of information about how to wire up things up for data to flow form the model to the view model to the controller to the view, I'm having a very hard time learning how to do other things while sticking to MVVM principles. One thing I'm really struggling with is setting a relationships between two managed objects after segueing to different view controllers. Let me explain...
When my app starts, it presents the first view controller which is backed by a view model which talks to a newly created NSManagedObject of type LiftEvent. From here, the user can segue to a Settings view controller which also has a view model, and from there they can segue to a table view, which also has a view model, and make a selection that needs to change an NSManaged var property of the model object. This property is a relationship to another NSManagedObject. Here's the flow:
I'm passing the managedObjectContext from the first view controller to the Settings view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let nav = segue.destinationViewController as? UINavigationController {
let vc = nav.topViewController as! SettingsViewController
vc.dismissalDelegate = self
let moc = viewModel.model.context
vc.moc = moc
}
and then pass it again to the last view controller where the user makes a selection and I want to set the relationship. It works, but I don't like it because I now have to import CoreData in the view controllers I'm passing it to.
The view controller on the far right is a UITableViewController. When the user selects one of the rows, I want to use that selection to grab the corresponding managed object and set it as one of the properties on the object that was created when the app launched. Here's the didSelectRowAtIndexPath method:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let defaultFormula = NSUserDefaults.formula()
guard indexPath.row != defaultFormula.rawValue else { return }
if let newDefaultFormula = FormulaName(rawValue: indexPath.row) {
NSUserDefaults.setDefaultFormulaName(newDefaultFormula)
}
if let selectedFormula = formulasArray[indexPath.row] {
// now what?
}
}
FormulaName is a enum of the possible names. The data source is formulasArray, an array of Formula managed objects. I initialize the array in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
formulasArray = dataManager.fetchSelectableFormulas(moc)
}
I see a few possible solutions in my head but I have no idea which of them, if any, are good choices:
pass the Formula object I picked out of the array back to the first view controller in unwind segues and set the relationship there
somehow get a hold of the LiftEvent from here, set the relationship, then tell the view model of the first view controller that the object has changed.
pass a reference to the LiftEvent object around instead of passing just the managedObjectContext property
I've spent countless hours trying to find examples, lessons, git repositories, SO threads, etc to figure this out that I don't know where to go from here. I'm not asking anyone to write the code for me, just steer me in a good direction and I'd be forever grateful.
I too have had issues finding concrete examples of MVVM in iOS, so i've kind of developed my own approach.
I have a couple recommendations (this is opinion to be clear).
Keep the NSManagedObject context in a CD access singleton for access on the main thread is really helpful. Assuming you're not using multiple databases or doing a bunch of things in the background, (which you can handle as well with the accessor if necessary) you will have 1 context with no context passing.
When using Segues, dependency injection is awesome. Rather than giving your new VC the minimum information it needs to find the data and set things up, simply set it's ViewModel in the Segue and build the VC to work only off of that. This is what makes ViewModels work so well. your View/ViewController doesn't need to to know anything about your model.
When dealing with unwinds, my approach is to simply make a "selectedItem" property in the desired VC and leave the responsibility of what to do to the VC to which it is unwinding. If necessary it can grab that value when the unwind occurs. This allows your choosing VC to operate the same way regardless of the behavior around it.

iOS 10 issue with storyboard custom view classes

Does anybody know why the ZoomingPDFViewer Apple Sample Code project no longer works? It was working prior to the iOS 10 release but now it keeps returning a unrecognized selector error when calling [PDFScrollView setPDFPage:].
It seems like the custom classes set in the storyboard are no longer being instantiated.
I had the same issue. It seems that the auto conversion to Swift 3 doesn't work well with IBs.
There are two options:
a) You can set the argument label--i.e.the parameter name to be shown externally--to _. For example,
#IBAction func setPDFPage(_ sender: AnyObject) {
// ...
}
b) You can remove the IBAction connection in the IB and re-set it. Notice instead of the previous setPDFPage(sender:) the connection will say setPDFPageWithSender(sender:) or something like that. (I didn't actually try it with the code in question, but writing from experience here.)
The same is true for segues.

UITextField always results in nil

I'm trying to get the information of a UITextField and then reuse that data from the UITextField with swift 2.0 and Xcode 7. But every time I run the app, the app crashes and says:
fatal error: unexpectedly found nil while unwrapping an Optional value
even though there is something in the textfield.
Someone knows how to solve this problem?
This is how my code looks:
#IBOutlet weak var entryAmount: UITextField! //TextField declaration
#IBAction func saveNewButtonTapped(sender: UIBarButtonItem) {
//update overall amount
let amount: String? = self.entryAmount.text! //Throws the error at this point
self.viewController.currentValue += amount
}
In another function, I wrote the same thing ("let amount: String? = entryAmount.text!") and when I printed "amount", it showed the right result. But as soon as I tried to use it further, "amount" was always set to nil, even tough it always printed the right result in the beginning.
I encounter the same thing with DatePicker, Switches and updating Button texts.
Someone knows something to help me? - Thanks
Btw I'm pretty new to swift and Xcode.
Update: Could it be because the TextField is in a ContainerView?
Update Two: I'm pretty much sure the problem is because of the ContainerView. I tried a TextField that isn't in a containerView and didn't get the error, it worked perfectly fine.
Please check in your Interface Builder that the UITextField is properly linked to your "entryAmount" IBOutlet. It might be the case that they are not properly connected and that's why the value is nil at runtime.
I could be wrong but what it seems like from your question is you're having trouble with scoping. Any variables you declare in a function are scoped only to that function.
Declaring amount in saveNewButtonTapped means amount is only set in that function. Using amount in another function will result in amount being nil. If you want to use variables between functions you need to declare them outside of a function.
In the example I have the var runningAmount. In the example, if I press the button that calls pressAnotherButton, I get the same fatal error you get because I'm unwrapping self.runningAmount before it's been set. But if I press the button that calls saveNewButtonTapped, even with nothing in the textfield, then press the button that calls pressAnotherButton, I get the output in the photo.