When I try to make an array with UIImageView s declared as #IBAction I receive the "Cannot use instance member within property initializer, property initializers run before self is available" compiler error. How do I workaround this?
As the error explains, you can't use instance members when you initialize the property outside of a method at the class level. Leave your array initializer at the class level and add your properties to it within a method.
var imageViews = [UIImageView]()
override func viewDidLoad(){
imageViews = [pos_1,pos2]
//or
imageViews.append(pos_1)
imageViews.append(pos_2)
}
You declared imageViews as a constant (a separate issue) then redeclared it again in viewDidLoad. You shouldn't need to redeclare it (or you could just declare once in viewDidLoad)
Related
As I'm working through a test-driven development exercise to gain better understanding of how objects can be initialized and used I've run across a test case where I create an object such as a UITableView but I didn't have to use the init function of UITableView.
Here is a sample of the code:
import XCTest
#testable import PassionProject
class ItemListDataProviderTests: XCTestCase {
var sut: ItemListDataProvider!
var tableView: UITableView!
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
sut = ItemListDataProvider()
tableView = UITableView()
sut.itemManager = ItemManager()
tableView.dataSource = sut
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func tests_numberOfSectionsIsTwo(){
let numberOfSections = tableView.numberOfSections
XCTAssertEqual(numberOfSections, 2)
}
As I'm learning the swift language and OOP in general, I know initializers come in different flavors and I ASSUMED that in order to use an instance we will need to initialize it. However above I was able to create a UITableView instance and store it's memory inside a variable. I was even allowed to access it properties such as tableview.datasource.
I'd like to know am I correct in thinking that in order to use instance of any type of class, it must be initialized (if the stored properties aren't set by default)?
What did I just do in layman terms? Did I just allocate the memory?
Where I became confused is when reading Apple's documentation, this class has an initializer which I never had to use because I never set the frame or the style: init(frame: CGRect, style: UITableViewStyle)
Thanks in advance for commenting or answering.
The expression UITableView() invokes the initializer for UITableView. In more general terms, <someobject>() invokes the initializer for that object that does not take any parameters:
init() {
//Initializer code goes here
}
You are correct that in order to use an object in Swift, you have to initalize it. Doing so both allocates the memory for the object and initializes it. Those two things can't be separated in Swift.
In Objective-C allocating and initializing objects are two distinct steps. You'll see the syntax SomeObject *object = [[SomeObject alloc] init] all over the place. That code invokes the SomeObject class's alloc method to allocate memory for the object, and then sends the newly allocated object an init method, which lets the object initialize it's variables and do other setup.
In Swift the system allocates the memory for you, and invoking an init method both allocates space for the object and initializes it.
I have two .swift files so I want one of them to modify the text of an IBoutlet label that is the other class.
Class 1:
class class1: UIViewController {
#IBOutlet var label1: UILabel!
}
Class 2:
class class2: SKScene {
var cool_variable = 1
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
}
by doing this I'm getting the following error message:
Instance member "label1" cannot be used on type "class2"
Thanks in advance!
The distinction and relationship between classes and instances is absolutely crucial to object-oriented programming. Thus, consider your proposed code:
func coolFunc () {
class1.label1.text = "\(cool_variable)"
}
The problem here is that class1 is the name of a class. (That would be more obvious if you just, please, follow the elementary rule that class names start with a capital letter.) But label1 is an instance property. Thus what you need here is the name of an instance of the class1 class — presumably a reference to the actual existing instance that is already part of the view hierarchy.
You never create an instance of class1 in class2 to access the variables.
My guess is that you are using Storyboards. In this case you wouldn't want to create an instance of class1. Instead you would use delegation (This would also be a good idea if you are not using Storyboards).
Delegation can be a complicated topic, so I will try to keep this simple.
First, you start with a protocol. Usually you name it something like <CLASS-NAME>DataSource, so you would do something like:
protocol class2DataSource: class {}
The class keyword is required for delegation protocols.
Then you would add the methods to it that you want called in other classes when you call a method in the class the protocol delegates for, so, for example, willCoolFunc:
protocol class2DataSource: class {
func willCoolFunc(with variable: Int)
}
You have the parameter so you can access the variable cool_variable as you are trying to.
Next, you need to create a a variable in class2 that is of type class2DataSource:
weak var dataSource: class2DataSource?
Make sure the variable is weak and an optional.
Next, call the method, you would do it in coolFunc:
func coolFunc () {
dataSource?.willCoolFunc(with: cool_variable)
}
Now you, to access cool_variable when the function is called, you need to implement class2DataSource on class1. Create an extension of class1 that implements class2DataSource and add the function willCoolFunc:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
}
}
Now you can access the variable cool_variable in class1! The reason why is because when you call class2.coolFunc(), the willCoolFunc method is called with cool_variable passed in. Any class that implements the class2DataSource can access cool_variable with the willCoolFunc method.
To finish of the method, here is what the extension would look like:
extension class1: class2DataSource {
func willCoolFunc(with variable: Int) {
self.label1.text = "\(variable)"
}
}
We are almost done, but not quite. We still have to set the class1 as the data source for class2DataSource. To do this, I will reference Nikolay Mamaev from this post:
Go to the Interface Builder.
Type "Object" in the search text field of the Objects Library and drag an 'Object' to your view controller containing that connects to
class1 (i.e. do the same as you add any view or view controller to
storyboard scene, with the exception you add not a view or view
controller but an abstract object).
In the left-side 'Scenes' panel of your storyboard, highlight just added Object; in right-side panel go to the 'Identity Inspector' and
type class2DataSource instead of pre-defined NSObject. In left side
panel ('Scenes'), 'Object' will be renamed to 'Class2 Data Source'
automatically.
In the left-side 'Scenes' panel of your storyboard, control-drag from your UIView [*not the controller*] to the 'Class2 Data Source';
in pop-up appeared, select dataSource outlet.
There! You now set class1's label1's text to the value of cool_variable when you call class2.coolFunc()!
I do not know the exact problem you're trying to solve, but I'm just going to consider the part that you want to access the variable in class1 in class2. There are two basic ways to go about this, one is Inheritance and the other one is by creating an object. These are basic Object Oriented Programming concepts which you need to be familiar with.
Swift does not support multiple inheritance. So that rules out inheritance for solving you problem, since you are inheriting two classes SKScene and UIViewController.
Create an object in the class1 and call the function coolFunc
class class1: UIViewController {
#IBOutlet var label1: UILabel!
func modifyLabel(){
let someObject = class2()
someObject.coolFunc()
}
}
Of course this solution might not be the one you're looking for. You will have to explain more about the problem you're facing if you need a solution that will work for you.
In a simple example like this, I can omit self for referencing backgroundLayer because it's unambiguous which backgroundLayer the backgroundColor is set on.
class SpecialView: UIView {
let backgroundLayer = CAShapeLayer()
init() {
backgroundLayer.backgroundColor = UIColor.greenColor().CGColor
}
}
But, just like in Objective-C, we can confuse things by adding local variables (or constants) named similarly. Now the backgroundColor is being set on the non-shape layer:
class SpecialView: UIView {
let backgroundLayer = CAShapeLayer()
init() {
var backgroundLayer = CALayer()
backgroundLayer.backgroundColor = UIColor.greenColor().CGColor
}
}
(this is resolved by using self.backgroundLayer.backgroundColor)
In Objective-C I always eschewed ivars for properties and properties were always prefixed with self for clarity. I don't have to worry about ivars in swift but are there other considerations for when I should use self in swift?
The only times self is required are when referencing a property inside a closure and, as you pointed out, to differentiate it from a local variable with the same name.
However, personally, I prefer to always write "self" because:
That is an instant and obvious sign that the variable is a property. This is important because it being a property means that its state can vary more widely and in different ways than a local variable. Also, changing a property has larger implications than changing a local variable.
The code does not need to be updated if you decide to introduce a parameter or variable with the same name as the property
Code can be easily copied in and out of closures that do require self
Most of the time we can skip self. when we access class properties.
However there is one time when we MUST use it: when we try to set self.property in a closure:
dispatch_async(dispatch_get_main_queue(), {
// we cannot assign to properties of self
self.view = nil
// but can access properties
someFunc(view)
})
one time when we SHOULD use it: so you don't mess a local variable with class property:
class MyClass {
var someVar: String = "class prop"
func setProperty(someVar:String = "method attribute") -> () {
print(self.someVar) // Output: class property
print(someVar) // Output: method attribute
}
}
other places where we CAN use self.
before property just to be expressive about were variable/constant comes from.
Looking at Ray Wenderlich's style guide
Use of Self
For conciseness, avoid using self since Swift does not require it to access an object's properties or invoke its methods.
Use self only when required by the compiler (in #escaping closures, or in initializers to disambiguate properties from arguments). In other words, if it compiles without self then omit it.
Swift documentation makes the same recommendation.
The self Property
Every instance of a type has an implicit property called self, which is exactly equivalent to the instance itself. You use the self property to refer to the current instance within its own instance methods.
The increment() method in the example above could have been written like this:
func increment() {
self.count += 1
}
In practice, you don’t need to write self in your code very often. If you don’t explicitly write self, Swift assumes that you are referring to a property or method of the current instance whenever you use a known property or method name within a method. This assumption is demonstrated by the use of count (rather than self.count) inside the three instance methods for Counter.
The main exception to this rule occurs when a parameter name for an instance method has the same name as a property of that instance. In this situation, the parameter name takes precedence, and it becomes necessary to refer to the property in a more qualified way. You use the self property to distinguish between the parameter name and the property name.
Here, self disambiguates between a method parameter called x and an instance property that is also called x:
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
print("This point is to the right of the line where x == 1.0")
}
// Prints "This point is to the right of the line where x == 1.0"
I'm going to go against the flow and not use self unless absolutely required.
The reason why is that two of the main reasons to use self is
When capturing self in a block
When setting self as a delegate
In both cases, self will be captured as a strong reference. This might be what you want, but in many cases, you actually want to use a weak one.
Therefor, forcing the developer to use self as an exception and not a rule will make this strong capture more conscious, and let him reflect on this decision.
As Apple documentation says in https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Methods.html
The self Property
Every instance of a type has an implicit property called self, which
is exactly equivalent to the instance itself. You use the self
property to refer to the current instance within its own instance
methods.
The increment() method in the example above could have been written
like this:
func increment() {
self.count += 1
}
In practice, you don’t need to write self in your code very often. If
you don’t explicitly write self, Swift assumes that you are referring
to a property or method of the current instance whenever you use a
known property or method name within a method. This assumption is
demonstrated by the use of count (rather than self.count) inside the
three instance methods for Counter.
The main exception to this rule occurs when a parameter name for an
instance method has the same name as a property of that instance. In
this situation, the parameter name takes precedence, and it becomes
necessary to refer to the property in a more qualified way. You use
the self property to distinguish between the parameter name and the
property name.
Here, self disambiguates between a method parameter called x and an
instance property that is also called x:
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
print("This point is to the right of the line where x == 1.0")
}
// Prints "This point is to the right of the line where x == 1.0"
Without the self prefix, Swift would assume that both uses of x
referred to the method parameter called x.
I would prefer to keep using self whenever I'm using a property to omit these misunderstandings.
As Nick said, in objective-c we had ivars + synthesized properties which gave the _internal variable names to delineate things. Eg.
#IBOutlet (nonatomic,strong) UITableView *myTableView;
resulting in _myTableView to be (preferably) referenced internally - and self.myTableView to be reference beyond the class. While this is pretty black and white, consider the exception when programmatically instantiating views, you can gain clarity/ simplicity / reduce boilerplate by removing self.
#interface CustomVC:UIViewController
{
UITableView *myTableView;
}
In swift, the public / internal properties clarify this scope.
If it's a public property that other classes will interact with err on self.
Otherwise if it's internal skip self and avoid the automatic repetition.
The compiler will catch you when it's needed.
// UIViewcontroller swift header
public var title: String? // Localized title for use by a parent controller.
public var navigationItem: UINavigationItem { get }
/// In your class
self.title = "Clarity"
self.navigationItem.leftBarButtonItem = UIBarButtonItem()
// In superclass
#property(nonatomic, copy) NSString *screenName // use self.screenName in swift subclass
#IBOutlet myTableView:UITableView // use self
public var myTableView:UITableView // use self
internal var myTableView:UITableView // skip self
var myTableView:UITableView // skip self
I have an app I'm working on that has various viewcontrollers that use the same constants.
ex.
let wrongAnswerBanner = UIImageView(image: UIImage(named: "torn_banner"))
I tried declaring the constant outside the viewcontrollers but whenever I try to call the constant by using self.
self.wrongAnswerBanner.hidden = false
I get the error: Value of type 'ViewController1' has no member 'wrongAnswerBanner'. How can I declare these constants without having to redeclare them within each individual viewcontroller?
You can declare the constant outside of a class scope and access it directly from any file inside the module.
File 1
let MyConstant = "MyConstant"
class A {
}
File 2
class B {
// use MyConstant directly (eg. print(MyConstant))
}
I usually do this for UITableViewCell identifiers. I declare them on top of my UITableViewCell subclass and use them on the ViewController file. It's worth nothing though (as other developers mentioned) that UIImageView might not be a good candidate for constants. You can also make use of Enums if that makes sense for your problem.
Just try to call the constant without self
Create a struct with constants:
struct Constant {
static let SomeConstant = "hey"
}
then you can get the value from any class by
let constant = Constant.SomeConstant
According to me you have to create one AppConstant.swift file in your application ,then just add this line in AppConstant.swift file
let wrongAnswerBanner = UIImageView(image: UIImage(named: "torn_banner"))
Then in any of controller you can easily access wrongAnswerBanner without using self
When I try to create an array of UIButtons in the ViewController
Instance member 'tile11' cannot be used on type 'ViewController'
It works when I create it inside a method, but I need the array in more than one method, and it's getting annoying to having to create the array again and again.
class ViewController: UIViewController {
let tileArray: Array<UIButton> = [tile11, tile12, tile13, tile21, tile22, tile23, tile31, tile32, tile33]
}
There isn't enough code to tell for sure, but I'm guessing tile11 and the other tiles are other instance variables (button outlets to be specific)? They would technically not be accessible until run-time. This is why you cannot instantiate tileArray like that, but why it works in a method. The only way to instantiate tileArray in line with the declaration is to use constants in the array. You should initialize tileArray in viewDidLoad().