Swift - Custom setter on property - swift

I am converting a project in to Swift code and have come across an issue in a setter. My Objective-C code looked like this:
- (void)setDocument:(MyDocument *)document
{
if (![_document isEqual:document]) {
_document = document;
[self useDocument];
}
}
and allowed my View Controller to run this each time the document was set (typically in the prepareForSegue: method of the presenting View Controller).
I have found the property observers willSet and didSet but they only work when the property is being updated, not when it’s initialised and updated.
Any ideas? Thanks
UPDATE
after trying get{} and set{} I get the EXC_BAD_ACCESS error
var document: UIDocument? {
get {
return self.document!
}
set {
self.document = newValue
useDocument()
}
}

You can't use set like that because when you call self.document = newValue you're just calling the setter again; you've created an infinite loop.
What you have to do instead is create a separate property to actually store the value in:
private var _document: UIDocument? = nil
var document: UIDocument? {
get {
return self._document
}
set {
self._document = newValue
useDocument()
}
}

Here's a Swift 3 version
var document : UIDocument? {
didSet {
useDocument()
}
}

Related

Property Observer in Swift

Any idea how I can return a UIView from within didSet ?
I have a method that returns a UIView.
I need to observe an Int and return a UIView as the Int changes. I have a didSet observer set, however, I get an error when trying to return the UIView.
Appreciate any help! Thanks.
func newUIView() -> UIView {
var newUIView = UIView()
return newUIView
}
var observingValue: Int = someOtherValue {
didSet {
//Xcode complains whether I use return or not
return func newUIView()
}
}
You say in a comment:
I guess my struggle is how to observe that value and react (update UI) to it accordingly
An observer is a perfectly good way to do that. But you don't return something; you call something. This is a very, very common pattern in Swift iOS Cocoa programming:
var myProperty : MyType {
didSet {
self.updateUI()
}
}
func updateUI() {
// update the UI based on the properties
}
At the time that the didSet code runs, myProperty has already been changed, so the method updateUI can fetch it and use it to update the interface.
What you are asking doesn't make any sense.
A didSet is a block of code you add to an instance variable that gets invoked when anybody changes the value of that variable. There is no place to return anything.
If you need code that changes an instance variable and returns a view, you need to write a function:
func updateObservingValue(newValue: Int) -> UIView {
observingValue = newValue
return newUIView()
}

Cannot assign to property: 'xxxx' is a get-only property

I'm using a computed property to get the last book in my books array. However, it seems I can't use this property to directly set a book's position property, as my attempt below shows:
struct Book {
var position: CGPoint?
}
class ViewController: UIViewController {
var books = [Book]()
var currentBook: Book {
return books[books.count - 1]
}
func setup() {
// Compiler Error: Cannot assign to property: 'currentBook' is a get-only property
currentBook.position = CGPoint.zero
}
}
The following works but I'd like it to be more readable and a single line.
books[books.count - 1].position = CGPoint.zero
I could use a function to return the current book but using a property would be cleaner. Is there another way?
The error occurs because you did not tell the compiler what to do if the value of currentBook is mutated. The compiler assumes it is immutable.
Just add a setter so that the compiler knows what to do when you set the value:
var currentBook: Book {
get { return books[books.count - 1] }
set { books[books.count - 1] = newValue }
}
Or, consider using books.last!:
books.last!.position = CGPoint.zero

Swift initialization stored property outside init method of class issue

I have an swift class
class ApplicationManager {
var fanMode: FanMode
init()
{
self.applyDefaultSettings()
}
func applyDefaultSettings()
{
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
self.fanMode = unwrappedFanMode
}
}
}
The code above throws this issue:
Use of 'self' in method call 'applyDefaultSettings' before all stored properties are initialized
What should I do here? So as message say I need to initialize all stored properties before I call any other method of class. So it means in init method I should initialize at least fanMode property. But I want to have method that apply kind of default settings for my properties to provide simple readability and clean code architecture. But maybe it's ok to use initializer of class to init all needed fields.
You also can do it by using this code:
var fanMode: FanMode = {
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
return unwrappedFanMode
} else {
return FanMode()//some default implementation
}
}()
It is readable as You want.
As per Apple documentation, Swift does not allow you to left uninitialised variables or constants. If you want to set some default settings then assign your variables with initial values that will act as your default settings and later you can change them.
All instance properties must be initialized in the init method. You can either move the initialization to the init (defaultMode would be your default value if userDefaults is nil):
init() {
fanMode = (userDefaults?.valueForKey(Consts.kStoredFanMode) as? FanMode) ?? defaultMode
}
Set a default value for that property, for example:
var fanMode: FanMode = defaultMode
Or you can make your fanMode nullable:
var fanMode: FanMode? = nil
You can use an implicity unwrapped optional. Just add a ! to the variable declaration.
class ApplicationManager {
var fanMode: FanMode! //Implicitly unwrapped optional.
init()
{
self.applyDefaultSettings()
}
func applyDefaultSettings()
{
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
self.fanMode = unwrappedFanMode
}
}
}
Basically it tricks xCode into telling it "Hey this variable is initialized and value will never be nil". But you want to be careful using these as if it does turn out to be nil your program will crash. But in your case it should be fine since you initialize it in the init method so it will never be nil before using it.

swift setter causing exc_bad_access

I have a simple class below
import Foundation
public class UsefulClass: NSObject{
var test:NSNumber{
get{return self.test}
set{
println(newValue)
self.test = newValue
}
}
override init() {
super.init()
self.test = 5;
}
}
and I'm initializing it here
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var testClass = UsefulClass()
}
}
But it results in xcode printing out 200 5s and then crashing due to EXC_BAD_ACCESS code = 2. Why does this happen?
#vadian has provided a solution in his answer, which should fix your problem. Let me just explain what's happening.
You have created a computed property, i.e. a property which is not backed by a variable, instead both the getter and the setter do some processing, usually on another stored property, in order to respectively return a value and set a new value.
This is your computed property:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Look at the getter implementation:
return self.test
What does it do? It reads the test property of the current instance, and returns it. Which is the test property? It's this one:
var test: NSNumber {
get { return self.test }
set {
println(newValue)
self.test = newValue
}
}
Yes, it's the same property. What your getter does is to recursively and indefinitely calling itself, until a crash happen at runtime.
The same rule applies to the setter:
self.test = newValue
it keeps invoking itself, until the app crashes.
Swift variables are synthesized properties by default.
In the most cases this is sufficient (it's recommended to prefer Swift types)
var test: Int
override init() {
super.init()
test = 5
}
If you need to do something after a variable is set, use
var test: Int {
didSet{
println("\(oldValue) - \(newValue)")
}
}
your code sets the variable permanently by calling the setter which calls the setter which …
It's an infinite loop; your setter is recursively calling itself.
var test: NSNumber {
set {
test = newValue
}
}
This compiles fine, and an Objective-C programmer might expect no loop due to instead setting a "backing ivar" such as _test rather than re-calling the setter method.
But property-backing instance variable _ivars do not exist in Swift for computed properties unless you create them yourself.

Swift property - getter ivar

Is there an ivar property we should use in a Swift getter? My code is causing the getter to call the getter until the program crashes:
var document: UIDocument? {
get {
return self.document
}
set {
self.document = newValue
useDocument()
}
}
Swift properties do not have the concept of separate, underlying storage like they do in Objective-C. Instead, you'll need to create a second (private) property and use that as the storage:
private var _document: UIDocument?
var document: UIDocument? {
get {
return _document
}
set {
_document = newValue
useDocument()
}
}
If all you're trying to do is call useDocument() after the document property is set, you can omit the getter, setter, and private property and instead just use willSet or didSet.
If what you are trying to achieve is add some custom processing when the property is set, you don't need to define a separate backing data member and implement a computed property: you can use the willSet and didSet property observers, which are automatically invoked respectively before and after the property has been set.
In your specific case, this is how you should implement your property:
var document: UIDocument? {
didSet {
useDocument()
}
}
Suggested reading: Property Observers
In your code there is an infinite recursion situation: for example, self.document inside the getter keep calling the getter itself.
You need to explicitly define an ivar yourself. Here is a possible solution:
private var _document:UIDocument?
var document: UIDocument? {
get {
return self._document
}
set {
self._document = newValue
useDocument()
}
}