Referencing UIView subclass .center with Swift 3 #keyPath - ios

In a view controller in my app, I'm reacting to changes to a view's positioning by key-value observing its center property, like this:
class CoordinatingViewController: UIViewController {
#IBOutlet weak var cardContainerView: CardView!
override func viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: "cardContainerView.center", options: [.new], context: nil)
}
}
This works just fine for now, but since that key path is a string, it can't be checked by the compiler. To mitigate this, I'd like to use Swift 3's #keyPath() syntax, which takes a compiler-checked key path and returns the proper corresponding string. However, when I change that line to use it like this:
addObserver(self, forKeyPath: #keyPath(cardContainerView.center), options: [.new], context: nil)
The compiler gives me 2 errors:
Type 'CardView!' has no member 'center'
Cannot refer to type member 'center' within instance of type 'CardView!'
I don't understand why I'm getting these errors, as center is a documented property of UIView, which CardView inherits from directly, which I can both read and write to outside the #keyPath() statement there. Furthermore, everything appears to work ok when I pass the key path directly as a string, as in my first example, which makes this even more confusing. How can I have the compiler check that key path for me?

It seems like Swift is not happy about the weak modifier. Remove it and the code will compile. From this, we can see that outlets are recommended to be strong, unless you actually have a retain cycle. See my answer here for how to find retain cycles.
In Swift 4, you will be able to do this, using the new \ key path syntax:
// no problems with "weak"!
let observation = observe(\.cardContainerView.center) { (object, change) in
...
}

Related

Swift Keypath with UIButtons

I'm trying to get a keypath to the selected property of an IBOutlet within a class. But get:
Type 'UIButton?' has no member 'isSelected'
directly accessing the UIButton.isSelected keypath works, but doesn't fulfill my usecase.
#objc class Demo: UIViewController{
#IBOutlet #objc dynamic weak var button: UIButton!
}
var demo = Demo()
print(#keyPath(UIButton.isSelected)) // no error
print(#keyPath(Demo.button.isSelected)) // error
print(#keyPath(demo.button.isSelected)) // error
what am I missing?
#keyPath is just syntactic sugar that creates a string value, While ensuring that the keyPath is valid for the object you specify; It helps to prevent crashes when using KVO, since it validates, at compile time, that your keyPath is valid, rather than crashing at runtime if it isn't.
Accordingly, you don't specify a keyPath on a particular instance, you specify it on the object type. This is why your first line works and the second two don't.
You specify the specific object instance on which you want to observe the keyPath when you call addObserver:
demo.addObserver(someObserver, forKeyPath: #keyPath(UIButton.isSelected), options: [], context: nil)
You could also say
demo.addObserver(someObserver, forKeyPath: "selected", options: [], context: nil)
with the same result
But if you accidentally typed "slected" instead of "selected" you wouldn't find out until your app crashed at runtime, while #keyPath(UIButton.isSlected) will give you a compiler error straight away.

Enabling two-way communication between two classes

So I have a custom view touchableView inside a ViewController.
touchableView informs ViewController of changes in its properties through a delegate protocol (ViewController being the delegate).
What is the best method to change properties of touchableView from ViewController (so the other way around)?
Is there a way to create a two-way delegate relationship between two classes?
Simply:
Assuming that you are already have touchableView instance in the ViewController, you should be able to set -or get-/call its properties and methods.
For instance, assume that you have the following method in touchableView class:
class func fromNib() -> TouchableView {
return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)![0] as! TouchableView
}
You should simply be able to:
let touchableView = TouchableView.fromNib()
thus:
// for example
touchableView.myProperty = "Hello"
I assumed that TouchableView has a string property called myProperty...
Remark:
For some reason, I suggest to implement some of property observers in TouchableView:
Property observers observe and respond to changes in a property’s
value. Property observers are called every time a property’s value is
set, even if the new value is the same as the property’s current
value.
They might be -somehow- useful to be implemented in your custom class. For clarity, let's consider -for instance- that if editing the value of myProperty should be effecting the background color of the view, it might be implemented as:
var myProperty: String = "Initial Value" {
willSet {
print("About to set value to: \(newValue)")
}
didSet {
backgroundColor = UIColor.red
}
}
Further Reading:
If you are looking for an advanced approach for two way bindings (Implementing MVVM), you might want to check this article.
I would also suggest to take a look at some frameworks that will be so helpful for such an approach, such as RxSwift, for a more simple framework, you might want to check ReactiveKit/Bond.

[SearchStockCell retain]: message sent to deallocated instance

I'm getting the following error:
SearchStockCell retain]: message sent to deallocated instance 0x7f9fa1922c00
but I am having a hard time tracing the issue because whenever I profile with zombies, it stops without any warning or error(2-3 secs).
I'm using realm for this project and the data parsing is performed at background.Not sure if this information is relevant.
Is there other way to track this? or is possible I use weak for tableview cell?
Updated
class SearchStockCell: SSBaseTableCell {
#IBOutlet var symbolLabel: UILabel!
#IBOutlet var marketLabel: UILabel!
#IBOutlet var priceLabel: UILabel!
var stock: StockInfo? {
willSet{ "About to step to \(newValue)"
if let aStock = newValue {
// add KVO on newValue
aStock.addObserver(self,
forKeyPath: "price",
options: NSKeyValueObservingOptions.New,
context: nil)
aStock.addObserver(self,
forKeyPath: "change",
options: NSKeyValueObservingOptions.New,
context: nil)
}
}
didSet { "Just stepped from \(oldValue)"
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "price")
}
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "change")
}
self.configureCell()
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "price" {
self.updatePrice()
}
if keyPath == "change" {
self.updateChange()
}
}
....
...
..
.
Here is the code happened in SearchStockCell.
I will fire an API to update my visible cells(it will update my realm) which later will prompt the changes on the SearchStockCell by KVO. Note that I can't really reload the table again because I need to maintain the position of tableview and there's thousands rows of data in it.
It is really hard to guess from code but would try my best to look answer
Please look for places
have used SearchStockCell as a property somewhere if yes check for attributes strong/weak. There is possible cycle of holding each other.
Check if you are using SearchStockCell object in block, if yes try using it as weak object. Also check for other things done inside the block.
you are using KVO, check if at any point of time is observer going out of memory.
Most likely issue which I can think of right is at some place you are assigning/using SearchStockCell object as weak/strong due to which ARC is handling retain count wrongly.
It looks like you're vastly overcomplicating this situation by adding and balancing KVO on these table cells.
You mentioned that you don't want to reload the table since you'll lose your position in the scroll view. Have you considered simply saving the scroll position of the table view before reloading and then re-setting it afterwards?
As a side note, Realm will soon introduce a feature to track insertions/updates/deletions on a table view data source, so hopefully once that's out, you could use that here instead (Disclaimer: I work for Realm).

IF Statement Incorrectly Evaluating to True when Using Optional Values in Swift

I have set up my view controllers so that they send a notification once their -viewDidLoad method is about to return. For example:
class MyViewController: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
//Do Stuff
var notificationCenter = NSNotificationCenter.defaultCenter();
notificationCenter.postNotificationName("AViewControllerDidLoadNotification", object: self);
}
}
My AppDelegate class is listening for this notification and implementing the method shown in this picture.
In case the picture isn't loading, the method takes the notification sent by the view controllers as it's only argument and then tests whether the UIViewController's title property has a non-nil value. If the title property is non-nil it logs the title.
However, as you can see in the debugger panel, the title property of the view controller is nil and the if statement is still evaluating to true.
I am admittedly new to optional values. But I have recreated this situation in a swift playground and the if statement evaluates to false. Any ideas?
You've gotten yourself into rather an odd situation with your very peculiar use of the expression notification.object?.title, because notification.object is not, of itself, a UIViewController. It is an AnyObject.
Now, an AnyObject has no known properties, so it has no title and your expression, it would seem, should not even compile. But, by a special dispensation coming from certain oddities of Objective-C, you are in fact allowed to ask about an AnyObject's properties anyway. But when you do, the result is itself an Optional, because no such property might exist.
Thus, you are actually testing, not the value of a view controller's title property, but whether this unknown object has a title property in the first place; and if in fact it does have a title property at all, the value of that title property is double-wrapped inside that Optional.
To see this clearly, just test this (silly) code:
let n = NSNotification(name: "Howdy", object: "Hi")
let t = n.object?.title
Look at what type t is. It is not a String?; it is a String??. That's your double-wrapped Optional. This means that it would be an Optional-wrapping-a-String in case this object turns out to have a title property, but just in case, that value has itself been wrapped in an Optional.
Thus, your test doesn't do what you want it to do. To do what you want to do, just speak much more plainly and simply. You need to cast the object to a UIViewController first, and then examine its title. Like this:
func aViewControllerDidLoad(notification:NSNotification) {
if let vc = notification.object as? UIViewController {
if vc.title != nil {
// ...
}
}
}

Subclassing UIView in Swift - error

I have a file called MyView.swift that just looks like this:
class MyView : UIView {
}
I also have a MyView.xib file with a view with class set to MyView.
In a view controller I have this:
var myView: MyView?
override func viewDidLoad() {
super.viewDidLoad()
self.myView = NSBundle.mainBundle().loadNibNamed("MyView", owner: nil, options: nil)[0] as MyView
self.myView.frame = frame;
self.view.addSubview(self.myView);
}
I get this error:
MyView? does not have a member named 'frame'
Why? MyView is a subclass of UIView...
You will have to implicit unwrap that optional, or use optional chaining.
self.myView!.frame = frame
Or utilize optional chaining.
self.myView?.frame = frame
// can do this now in Swift.
The first option is more dangerous because when you forcefully unwrap an optional, and it is nil, a runtime error will occur.
Edit I apologize, optional chaining is not the right tool for the job here. It is the right tool if you wanted to query the value of the frame. Alternatively, you could use the if let syntax.
if let optionalView = self.myView {
var queriedFrame = optionalView.frame
}
MyView is a subclass of UIView, but MyView? is an Optional which has only two possible cases:
(1) it is nil, or
(2) it holds a wrapped value of type MyView
...and in either case, it is still an Optional, which means it doesn't have a member named 'frame' :) though if it's holding a wrapped value of MyView then that wrapped value does!
To get at that wrapped value (assuming it has one) it is necessary for you to either explicitly unwrap it with ! or for you to use Optional Binding (if let) in accessing its properties.

Resources