79

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);
    }
}
AmitaiB
  • 1,656
  • 1
  • 19
  • 19
Lukas Köhl
  • 1,569
  • 2
  • 15
  • 28
  • 2
    "I use Xcode Beta5" Why? Xcode 6 has gone GM. Always use the latest version, esp. as the Swift language keeps changing. – matt Sep 15 '14 at 18:20
  • Ya I know but I came from a vacation and did`t done that yet. But updating won't solve the problem. – Lukas Köhl Sep 15 '14 at 18:22
  • 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. – matt Sep 15 '14 at 18:24
  • 2
    Post the entire source file. – rob mayoff Sep 15 '14 at 18:24
  • 1
    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... – matt Sep 15 '14 at 18:25
  • @matt 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. –  Sep 15 '14 at 19:41
  • @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. – matt Sep 15 '14 at 20:09
  • @matt 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. –  Sep 15 '14 at 20:19
  • @matt I edited my question and there is know the complete code. Also I will update to the lastest Xcode version tomorrow. – Lukas Köhl Sep 15 '14 at 20:22
  • possible duplicate of [ViewControl.Type does not have a member named](http://stackoverflow.com/questions/25855137/viewcontrol-type-does-not-have-a-member-named) – In your case the problem is at: `var panzer = UIImageView(frame: CGRectMake(corX, corY, 30, 40))`. – Martin R Sep 15 '14 at 20:25

4 Answers4

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 6
    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. – Martin R Sep 15 '14 at 21:06
  • 1
    @MartinR Good call. All fixed now! – matt Sep 16 '14 at 01:13
  • @matt I still get an error with that change: `'ViewController -> () -> ViewController!' does not have a member named 'corX'` – Mike S Sep 16 '14 at 02:42
  • 2
    @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). – matt Sep 16 '14 at 03:43
  • 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. – kelin Jun 27 '17 at 21:06
  • @kelin 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? – matt Jun 29 '17 at 17:20
  • 3
    @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. – kelin Jun 30 '17 at 09:45
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'
pkamb
  • 33,281
  • 23
  • 160
  • 191
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...

Community
  • 1
  • 1
mfaani
  • 33,269
  • 19
  • 164
  • 293
-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()
pkamb
  • 33,281
  • 23
  • 160
  • 191
CrazyPro007
  • 1,006
  • 9
  • 15