How to initialize properties that depend on each other
Asked Answered
H

4

80

I want a picture to move to the bottom. If I press a button the pic should move down by 1.

I added the picture and a button:

var corX = 0
var corY = 0

var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton

var image = UIImage(named: "panzerBlau.jpg");
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40));  //

override func viewDidLoad() {
    super.viewDidLoad()

    panzer.image = image;    //
    self.view.addSubview(panzer);    //

    runter.frame = CGRectMake(100, 30, 10 , 10)
    runter.backgroundColor = UIColor.redColor()
    view.addSubview(runter)
    runter.addTarget(self, action: "fahren", forControlEvents:UIControlEvents.TouchUpInside)
}

At least I said in function "fahren" to move the picture down by 1.

func fahren(){
    corY += 1
    panzer.frame = CGRectMake(corX, corY, 30, 40) //
    self.view.addSubview(panzer);
}

So my problem is: I get several errors with these corX and corY thing. Without them it works perfectly but than its like a single-use button. The errors are: ViewController.Type does not have a member named corX and ViewController.Type does not have a member names panzer Where I get the errors I made // to show in which lines.

PS: I use Xcode Beta5

Here's the complete code without anything else:

import UIKit

class ViewController: UIViewController {

    var corX = 0
    var corY = 0
    var runter: UIButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
    var image = UIImage(named: "panzerBlau.jpg");
    var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40));

    override func viewDidLoad() {
        super.viewDidLoad()
            panzer.image = image;
            self.view.addSubview(panzer);

        runter.frame = CGRectMake(100, 30, 10 , 10)
        runter.backgroundColor = UIColor.redColor()
        view.addSubview(runter)
        runter.addTarget(self, action: "fahren", forControlEvents:UIControlEvents.TouchUpInside)
    }

    func fahren(){
        corY += 100
        panzer.frame = CGRectMake(corX, corY, 30, 40)
        self.view.addSubview(panzer);
    }
}
Headspring answered 15/9, 2014 at 18:16 Comment(10)
"I use Xcode Beta5" Why? Xcode 6 has gone GM. Always use the latest version, esp. as the Swift language keeps changing.Apace
Ya I know but I came from a vacation and did`t done that yet. But updating won't solve the problem.Solothurn
It sounds like you've put your code in the wrong place, e.g. var corX = 0 is not at the top level of the class declaration.Apace
Post the entire source file.Alfie
Can you show the surrounding context? (Yeah, like what @robmayoff said.) And please, work harder at formatting it correctly this time. No one likes a poster who is too lazy to format his code...Apace
@Apace AFAIK the GM version is only for iOS so if you want to do Mac development you still need to use the beta. I agree on using the latest version that best fits your needs.Brogue
@Graff but then you would use Xcode 6.1 beta 1 (where Swift has changed still more). In any case you certainly would not use beta 5, which has been superseded by two further seeds (6 and 7) and the GM - with very significant changes.Apace
@Apace Hmm I missed the Xcode 6.1 beta 1 so thanks for pointing it out. As I said, using the latest version is a good thing! I'll upgrade to that for my MacOS development. Same goes for iOs development, later is a good thing with a developing language.Brogue
@Apace I edited my question and there is know the complete code. Also I will update to the lastest Xcode version tomorrow.Solothurn
possible duplicate of ViewControl.Type does not have a member named – In your case the problem is at: var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40)).Probe
A
67

@MartinR has pointed out the major issue here:

var corX = 0
var corY = 0
var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40))

The problem is that a Swift default initializer cannot refer to the value of another property, because at the time of initialization, the property doesn't exist yet (because the instance itself doesn't exist yet). Basically, in panzer's default initializer you are implicitly referring to self.corX and self.corY - but there is no self because self is exactly what we are in the middle of creating.

One workaround is to make the initializer lazy:

class ViewController: UIViewController {
    var corX : CGFloat = 0
    var corY : CGFloat = 0
    lazy var panzer : UIImageView = UIImageView(frame: CGRectMake(self.corX, self.corY, 30, 40))
    // ...
}

That's legal because panzer doesn't get initialized until later, when it is first referred to by your actual code. By that time, self and its properties exist.

Apace answered 15/9, 2014 at 20:55 Comment(7)
Have you tried to compile that? First, it is lazy without @ in the current Xcode version, and even then I still get the same error message.Probe
@MartinR Good call. All fixed now!Apace
@Apace I still get an error with that change: 'ViewController -> () -> ViewController!' does not have a member named 'corX'Scatology
@MikeS It sounds like you typed it rather than copying and pasting. You mustn't leave anything out. For example if you omit the UIImageView type declaration you'll get that error. Or if you omit the lazy declaration you'll get that error. It is all necessary. Copy and paste right out of your browser into your code. It compiles just fine in the current version of Xcode (Xcode 6 GM).Apace
This solution doesn't work. I have similar error trying to init label height constant using font constant. If I change height declaration to lazy var I will still be getting the same "Can't use instance member" error. Downvoted to prevent misleading.Coke
@Coke You can downvote if you like, but I assure you my answer works for the question that it answers. If you have a different problem, wouldn't it be better to ask a question about that than to assume I'm wrong when I'm not?Apace
@matt, your instructions don't work, just accept it. Only pkamb tells about 3 conditions that should be met to remove the compiler error: lazy, .self and : Type. Although you used : Type you didn't tell that this is important. And you answer is much longer and harder to read. I think it isn't fair that it has more upvotes.Coke
H
10

Your dependent property needs to be:

  1. lazy
  2. Have an explicit : Type
  3. Use self. to access other properties

Example:

let original = "foo"

// Good:
lazy var depend: String = self.original

// Error:
     var noLazy: String = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
lazy var noType         = self.original // Error: Value of type '(NSObject) -> () -> URLData' has no member 'original'
lazy var noSelf: String =      original // Error: Instance member 'original' cannot be used on type 'YourClass'
Hypnotic answered 9/2, 2017 at 8:12 Comment(0)
P
1

I'm addressing the title of the question:

Both lazy and computed properties help you deal with when the initial value for a property is not known until after the object is initialized. But there are some differences. I've highlighted the differences with bold.

If you simply need to initialize a variable after some other variable(s) is initialized then you should use lazy ie if the point is to simply add a delay (so all required properties get initialized before) then using lazy is the right way to go for it.

But if you need to constantly change a variable based on another, then you need a computed property that would work both ways:

  • if the computed property set then it sets the variables its related stored properties
  • if the stored properties are set (or are reset again) then it will trigger a change in then computed property.

if you change the lazy property's value it won't affect the storied properties that it was based on. see here


A good example for using a lazy property would be that once you have firstName & lastName then you would lazily instantiate a fullName and likely you would never change the firstName lastName of your object your fullName is a onetime only... Or perhaps something that can only be done by lazy properties is that up until you don't access the property it won't ever get initialized, therefore this would decrease the initialization load of your class. Loading a heavy calculation.

Additionally using the lazy will signal to other developers: "Hey first go read about the other properties and understand what they are...then come to this lazy property...since the value of this is based on them + this is likely a heavy computation that shouldn't be accessed too early..."

As for computed property a good example would be if you set the temperature to Fahrenheit then you also want your celsius temperature to change its value...and if you set the celsius temperature then again you want to change your Fahrenheit value.

As a result computed property would add extra computation...and if your computation is very simple and isn't called too frequently then it's nothing to worry about but if it get's called too often or is very CPU-consuming then it might be better to think of other options...

Piet answered 3/5, 2017 at 16:9 Comment(0)
E
-1
//
//  ViewController.swift
//
//  Created by Shivank Agarwal on 19/05/18.
//  Copyright © 2018 Shivank Agarwal. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    var corX = 0
    var corY = 0
    var runter: UIButton = UIButton()
    var image = UIImage(named: "panzerBlau.jpg")
    var panzer = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        panzer.image = image;
        self.view.addSubview(panzer);
        panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
        runter.backgroundColor = UIColor.red
        view.addSubview(runter)
        view.addSubview(panzer)
        runter.addTarget(self, action: Selector(("fahren")), for:UIControlEvents.touchUpInside)
    }

    private func fahren(){
        corY += 100
    }

    private func updatePanzerFrame(){
        panzer.frame = CGRect(x: CGFloat(corX), y: CGFloat(corY), width: 30, height: 40)
    }
}

Note: Do not add panzer imageView every time when user tap only add it on viewDidLoad()
Ericerica answered 19/5, 2018 at 16:53 Comment(1)
How does this answer apply to this question?Hypnotic

© 2022 - 2024 — McMap. All rights reserved.