iOS Swift Variable are not accessible in functions - ios

Im doing this app and When I call a variable outside a function it won't recognize it as shown bellow
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var monsterImg: MonsterAnimation!
#IBOutlet weak var heartImg: DragImages!
#IBOutlet weak var foodImg: DragImages!
// Skulls on Top Board
#IBOutlet weak var penalty1Img: UIImageView!
#IBOutlet weak var penalty2Img: UIImageView!
#IBOutlet weak var penalty3Img: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Skulls Alpha Manipulation
let DIM_ALPHA: CGFloat = 0.2
let OPAQUE: CGFloat = 1.0
let MAX_PENALTIES = 3
var penalties = 0 // VARIABLE NOT ABLE TO BE SEEN IN FUNCTION ALSO ABOVE ^^^
foodImg.dropTarget = monsterImg
heartImg.dropTarget = monsterImg
// Skulls Alpha Initalzation
penalty1Img.alpha = DIM_ALPHA
penalty2Img.alpha = DIM_ALPHA
penalty3Img.alpha = DIM_ALPHA
NSNotificationCenter.defaultCenter().addObserver(self, selector: "itemDroppedOnCharacter:", name: "onTargetDropped", object: nil)
}
func itemDroppedOnCharacter(notif: AnyObject) {
}
func startSkullTimer() {
var skullTimer: NSTimer!
if skullTimer != nil {
skullTimer.invalidate()
}
skullTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "changeGameState", userInfo: nil, repeats: true)
}
func changeGameState() {
penalties++ // HERE IT"S NOT ABLE TO SEE THE VARIABLE ABOVE
}
But When I put it inside the function it works but then the other variables and things don't recognize it because its in the function. How do I fix this so the function will recognize the Variables out side it?

Just declare your variables outside of viewDidLoad() together with your IBOutlets.
If you want to give them initial values you can do it either direct when declaring or just give a type and set the value again in viewDidLoad()
For more information on the topic of access control, read up on the Apple docs:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html

Welcome to SO. You are facing the issue of "scope". A variable defined in an inner scope like a function is only accessible inside that inner scope (the function). Once you exit the function, the variable "goes out of scope" and is forgotten.
Think of scope as groups of circles. A variable defined in a circle (scope) is only visible inside that circle. Circles inside a larger circle have access to the variables defined in the outer circle. (Inner scope has access to enclosing scope.)
Functions have a "containing scope" in which they are defined. If a function is an instance method of a class, then it has access to instance variables from the object.
Thus, if you need two functions to both have access to the same variable, that variable needs to be in a common, outer scope. (Or if one function calls the other you can pass the value as a parameter.) Again, for instance methods, it might make sense to make the value that both functions need an instance variable of the class.
In your case, these are instance methods of a view controller, so you just need to make your penalties variable an instance variable of your view controller. You do that by moving it's declaration outside of the function and into the top part of the declaration of your ViewController class.

Related

UIView to UIViewController communication via protocol not working

maybe I am missing something really fundamental here, but after staring at the code for an hour or so, my brain is going trough cycles and I would appreciate a fresh glance at this problem.
I have the following UIView:
import UIKit
protocol DetailViewWillShowUpDelegate {
func sendDetailOpened(_ openedBool: Bool)
}
class locationXIBController: UIView {
#IBOutlet weak var loationLabel: UILabel!
#IBOutlet weak var vsedniOteviraciDobaLabel: UILabel!
#IBOutlet weak var prijmajiKartyLabel: UILabel!
#IBOutlet weak var detailViewButtonOutlet: UIButton!
#IBOutlet weak var backgroundViewButton: UIButton!
let openedBool = true
var detailViewWillShowUpDelegate: DetailViewWillShowUpDelegate?
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func vecerkaDetailButtonPressed(_ sender: UIButton) {
detailViewWillShowUpDelegate?.sendDetailOpened(openedBool)
print("pressed")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = detailViewButtonOutlet.hitTest(convert(point, to: detailViewButtonOutlet), with: event) {
return result
}
return backgroundViewButton.hitTest(convert(point, to: backgroundViewButton), with: event)
}
}
Now the problem is, that when I call/press the vecerkaDetailButtonPressed function I get "pressed" output in the console but the protocol for some reason doesn't go trough.
The other side looks like this (stripped for simplicity):
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
let locationXIB = locationXIBController()
let isVecerkaDetailOpened = false
override func viewDidLoad() {
locationXIB.detailViewWillShowUpDelegate = self
}
extension MapViewController: DetailViewWillShowUpDelegate {
func sendDetailOpened(_ openedBool: Bool) {
isVecerkaDetailOpened = openedBool
print("success")
}
}
I know the protocol value at the moment of execution is nil. As I said, any help is appreciated, thanks!
First, a couple of naming convention issues:
The name locationXIBController is a bad choice for a UIView object. It is a view object, not a controller object.
Second, class names in Swift should start with an upper-case letter. So LocationXIBView would be a much better name for that view class.
Next, your code
let locationXIB = locationXIBController()
...is wrong. That creates a brand-new instance of your locationXIBController class that you never install in your view hierarchy. You should make that line an IBOutlet:
#IBOutlet weak var locationXIB: locationXIBController!
And then you should control-drag from the locationXIBController in your StoryBoard onto the outlet in your view controller. That will cause Interface Builder to connect the outlet.
Now when you run your program the variable locationXIB will be connected to the locationXIBController view from your storyboard/XIB when it's loaded.
In addition to the answer of #Duncan C, you might check whether you need super.viewDidLoad() at the top of the viewDidLoad() method in the MapViewController class? Not doing that can lead to quirky things in your app.
I asked:
So does detailViewWillShowUpDelegate actually point at anything, or is it nil?
And you replied:
I just tried debugging and it is actually nil
So that's the problem... you need to set detailViewWillShowUpDelegate to point to a valid delegate object. This is often done in the .xib file or storyboard, and sometimes people forget to make that connection, so check there if it makes sense. Else you'll just need to get a reference to the delegate at some point before the code in question can run and set it up.
Answer to the credit of #Paulw11
I finally managed to get it working by communicating like so:
step 1) 1:1 communication via protocol between MKAnnotation and MKAnnotationView
step 2) 1:1 communication via protocol between MKAnnotationView and MapViewController passing the same data
Finally works like a charm, thanks!

Swift 2.2 - Updating UILabel throws "unexpectedly found nil while unwrapping an optional value"

I am new to iOS development so forgive me if I'm missing something obvious. I have a view controller that contains a subview in which I've created a numpad, and for the time being I want to give the numpad view its own UIView subclass because I want to do a few different things with it. Right now the numpad is just creating a string from the keys that get pressed, and I've set up a delegate to pass that string anywhere else I want to use it (though I've also tried accessing the raw input directly in the view controller with let a = subview(); label.text = a.rawInput).
Whenever I try to set the text of the UILabel in the view controller to the subview's raw input, whether by delegation or directly, the UILabel is found to be nil and throws the error in the title.
Things I've tried:
Setting the text inside a viewDidLoad override, and outside of it
Setting a variable (testInput) inside the view controller to adopt the subview's raw input and setting the label text to that (I've confirmed that the variable inside the view controller gets properly set, so no delegation issues)
Using didSet on the testInput variable both to set label text to testInput and to try calling viewDidLoad and set the label text in there (printing testInput inside this didSet does print the right string, FWIW)
Deleting and relinking the IBOutlet for my label
Strong and weak storage for the IBOutlet variable
Trying to do the same thing in another subview within the view controller, in case for some reason it was the view controller's own fault
Searching everywhere for a solution that works
I'm stumped. Here is my relevant numpad code:
import UIKit
protocol NumpadDelegate {
func updateInput(input: String)
}
class Numpad: UIView {
// MARK: UI outlets
#IBOutlet weak var decButton: UIButton!
// MARK: Properties
var rawInput: String = ""
var visibleInput: String = ""
var calcInput: String = ""
var operandReady = 1
var percentWatcher = 0
var delegate: NumpadDelegate? = BudgetViewController()
// MARK: Functions
func handleRawInput(str: String) {
rawInput += str
print("numpad input is \(rawInput)")
delegate?.updateInput(rawInput)
}
And here is the view controller code:
import UIKit
class BudgetViewController: UIViewController, NumpadDelegate {
// MARK: Properties
//#IBOutlet weak var transactionValueField: UITextField!
#IBOutlet weak var remainingCashForIntervalLabel: UILabel!
#IBOutlet weak var intervalDenoterLabel: UILabel!
#IBOutlet weak var currencyDenoterLabel: UILabel!
#IBOutlet weak var mainDisplayView: TransactionType!
#IBOutlet weak var inactiveInputView: InactiveInput!
#IBOutlet weak var numpadView: Numpad!
#IBOutlet weak var rawInputLabel: UILabel!
var remainingCashForInterval = 40
let display = TransactionType()
var testInput = "" {
didSet {
viewDidLoad()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// let numpad = Numpad()
// numpad.delegate = self
// print("\(numpad.delegate)")
self.rawInputLabel.text = testInput
}
func updateInput(input: String) {
print("view controller now has \(input)")
display.mainInput = input
testInput = input
}
As a side note, in case you noticed that my protocol isn't a class type, for some reason adding : class to it and declaring my delegate as a weak variable prevents the delegation from working. Any suggestions there?
You assigned the delegate like so:
var delegate: NumpadDelegate? = BudgetViewController()
That doesn't reference the view controller whose scene was presented, but rather a new blank one. And that's why when you used weak, why it was deallocated (because that orphaned instance of the view controller has no strong references to it).
You should define the protocol to be a class protocol again, and define delegate to be:
weak var delegate: NumpadDelegate?
And then, in the view controller's viewDidLoad, uncomment the line that sets that delegate:
numpadView.delegate = self
But, do not uncomment the line that says numpad = Numpad(); that is incorrect as that creates yet another Numpad instance. But you do want to set the delegate of the existing Numpad, though.
Both of these issues (namely, getting a reference to the view controller that is to be the delegate of the Numpad view; and getting a reference to the Numpad view that the storyboard presented) suggest some misunderstanding about the the process of presenting a storyboard scene.
The process is basically as follows:
the view controller is instantiated, using whatever class you specified as the base class for that scene;
its root view, as well as all of the subviews on that scene will be instantiated for you;
the storyboard will hook up the IBOutlet references in the scene's base class to the outlets you created; and
the view controller's viewDidLoad is called.
That's an oversimplification, but that's the basic process.
But the key is that all of these view controllers and views that are referenced on the storyboard scene are created for you. You don't want to try to create any of these yourself (and the presence of the () at the end of BudgetViewController() or Numpad() says "create a new instance of x", which is not what we want to do here).
So, when we need to get a reference to the view controller so that we can programmatically specify the delegate for one of the views, you can do this in viewDidLoad, at which point self references the view controller that the storyboard instantiated for us. We don't want to instantiate a new one. Likewise, when you want to reference the Numpad that the storyboard instantiated for us (in order to hook up its delegate), you use the IBOutlet you hooked up in Interface Builder, rather than programmatically instantiate a new Numpad with Numpad().

Swift setting variable inline or in function?

What is the Swift standard for setting a variable you already know the value for? Here are the 2 different ways I'm thinking of.
Option 1: Declaring the variable in the class and then setting it in the ViewDidLoad method
class ViewController: UIViewController {
var refreshControl: UIRefreshControl!
var sampleString: String!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
sampleString = "Hello"
}
}
Option 2: Declaring the variable in the class and setting it inline
class ViewController2: UIViewController {
var refreshControl = UIRefreshControl()
var sampleString = "Hello"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Which is the preferred way to do this in Swift? Thanks in advance!
First of all, you have two fundamentally different types in your two examples. In the first example, the type is an implicitly unwrapped optional String (i.e., String!), which means it can accept the nil value. In the second example, it is just String. If the value does not need to be nil assignable, the second option is better.
With regard to your actual question. I would say the second option is preferable, as you initialize the value earlier and there is no chance that you will use it before it is initialized. This would be equivalently good to declaring the type as String and deferring the initialization to an init method.
The viewDidLoad method is only useful for UIViewController instances, and doesn't get invoked until the view is loaded (which typically is during presentation). Waiting to initialize a value until then is probably not preferred and wouldn't be useful in objects that don't subclass UIViewController.

will create a view in code, how to delcare it in Swift 1.2

I have to fix another developer's code by adding a dimmedView. I'd like to create this all in code. I understand the ! for an optional value but how would I declare the following view. It doesn't seem like I would need to initialize it but the compiler wants it initialized. How would I deal with this?
class EKViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var mainTV: UITableView!
var dimmedView:UIView
....
func renderOverlayNav(){
println("YYY about to render outlay")
self.dimmedView=UIView(frame: CGRectMake(0.0, 0.0, self.screenWidth, self.screenHeight))
Any instance variables declared within a class have to either be optional, have an initial value, or instantiated inside the init method.
var dimmedView = UIView()
or
var dimmedView: UIView?
or
init() {
dimmedView = UIView()
}
You must initialize all properties in Swift in your init. To get around this you can make the dimmedView an optional which will set it's value to nil or you can initialize it like so var dimmedView = UIView()

Swift why my function call only works in the viewDidLoad

A conceptual question. Why my function calls only work inside the viewDidLoad?
import UIKit
class ViewController2: UITableViewController, UITableViewDataSource {
#IBOutlet var tableview2: UITableView!
var forceView2 = [Float]()
var stiffView2 = [Float]()
var springNumView2 : NSInteger = NSInteger()
override func viewDidLoad() {
super.viewDidLoad()
println(self.forceView2)
println(self.stiffView2)
println(self.springNumView2)
var Answer = calculate(forceView2, stiffView2, springNumView2)
println(Answer.self)
}
If var Answer = calculate(forceView2, stiffView2, springNumView2) is placed above the viewDidLoad, an error of 'ViewController2.type does not have a member named 'forceView2'.
If you move that line out of the method scope, you are actually turning a local variable into a property. This way the Answer property is visible to all instance methods, but also accessible from outside.
The problem in your case is that the property is initialized with the return value of an instance function, which indirectly references to self - but swift doesn't allow referencing to self during the initialization, that's the reason why you have a compilation error.
You are trying to call a function in the class body and not within a method that's why.
You can of course create a computed property. A property that returns a calculated value:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID259
Something along these lines should work:
var answer : [Float] { return calculate(forceView2, stiffView2, springNumView2) }

Resources