How do I pass a class as a parameter in Swift? - swift

I want to write a function with the below signature:
func viewHasSuperviewOfClass(view: UIView, superclass: AnyObject.Type) -> Bool {
return view.superview is superclass
}
But it won't compile. What am I doing wrong? How do I pass the superclass and treat it as a parameter?

Pass the superclass as AnyClass and use isKind(of:) to test it:
func viewHasSuperviewOfClass(view: UIView, superclass: AnyClass) -> Bool {
return view.superview?.isKind(of: superclass) ?? false
}
Since view.superview is an optional you need to unwrap it. Using optional chaining will return nil if there is no superview, so use the nil coalescing operator ?? to return false if there is no superview.
Example:
let button = UIButton()
let label = UILabel()
label.addSubview(button)
viewHasSuperviewOfClass(view: button, superclass: UILabel.self) // true
viewHasSuperviewOfClass(view: label, superclass: UIButton.self) // false
It will read a little better if you make the function signature this:
func view(_ view: UIView, hasSuperviewOfClass superclass: AnyClass) -> Bool {
return view.superview?.isKind(of: superclass) ?? false
}
// example call
view(button, hasSuperviewOfClass: UILabel.self)

Related

Custom UIControl subclass with RxSwift

I am creating a custom subclass of UIControl (I need to override its draw method) and I want to add RxSwift to bind its isSelected property to my model.
So far so good. This works fine.
My problem is how can I do to change the value isSelected property in response of user touchUpInside event?.
My first try was to use the addTarget method of UIControl, but changing the value of isSelected programmatically is not reported by the ControlProperty (as stated in the doc). But I can figure another way to resolve this.
Any help appreciated.
Source code of the subclass:
class SYYesNoButton: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
// subscribe to touchUpInside event
addTarget(
self,
action: #selector(userDidTouchUpInside),
for: UIControlEvents.touchUpInside)
}
func userDidTouchUpInside() {
// change the value of the property
// this does not work,
// the change is not reported to the ControlProperty
// HOW CAN I CHANGE THIS ??
self.isSelected = !isSelected
}
}
Extensions to add reactive support:
extension SYYesNoButton {
var rx_isSelected: ControlProperty<Bool> {
return UIControl.valuePublic(
self,
getter: { (button) -> Bool in
return button.isSelected
},
setter: { (button, value) in
button.isSelected = value
})
}
}
extension UIControl {
static func valuePublic<T, ControlType: UIControl>(_ control: ControlType, getter: #escaping (ControlType) -> T, setter: #escaping (ControlType, T) -> ()) -> ControlProperty<T> {
let values: Observable<T> = Observable.deferred { [weak control] in
guard let existingSelf = control else {
return Observable.empty()
}
return (existingSelf as UIControl).rx.controlEvent([.allEditingEvents, .valueChanged])
.flatMap { _ in
return control.map { Observable.just(getter($0)) } ?? Observable.empty()
}
.startWith(getter(existingSelf))
}
return ControlProperty(values: values, valueSink: UIBindingObserver(UIElement: control) { control, value in
setter(control, value)
})
}
}
Thanks for all.
Once you have an actual UIControl, there's an even nicer way to a "native" RxCocoa extension called a ControlProperty using a helper method in RxCocoa.
For example:
extension Reactive where Base: someControl {
var someProperty: ControlProperty<Float> {
return controlProperty(editingEvents: .valueChanged,
getter: { $0.value },
setter: { $0.value = $1 })
}
}
This will expose the current value from the getter block whenever the specified UIControlEvent is fired, and will also set the value whenever some stream is bound to it.
It sort of acts like an Observable and Observer type together - you can observe its value, but can also subscribe to it.
If you are subclassing from UIControl, then you are making your own control class and you have to override one or more of beginTracking(_:with:), continueTracking(_:with:), endTracking(_:with:), or cancelTracking(with:) to make the control work the way you want. Then call sendActions(for:) with the correct event. The guts of a UIControl would not have Rx in it.
Taking a queue from UIButton, your control should not select itself, although it can highlight and unhighlight itself (when the user's finger is on it for example.)
Once you have properly created your UIControl, code outside the control can use Rx to observe it with no extra work on your part.
The following works (Updated for Swift 5/RxSwift 5):
class ViewController: UIViewController {
#IBOutlet weak var yesNoButton: SYYesNoButton!
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
yesNoButton.rx.controlEvent(.touchUpInside)
.scan(false) { v, _ in !v }
.bind(to: yesNoButton.rx.isSelected)
.disposed(by: bag)
}
}
#IBDesignable
class SYYesNoButton: UIControl {
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isSelected ? .green : .red
}
override var isSelected: Bool {
didSet {
super.isSelected = isSelected
backgroundColor = isSelected ? .green : .red
}
}
}

Searching for objects matching a particular type

I've got an NSViewController extension that iterates through all its descendant child view controllers looking for the first view controller that passes a particular test (specified by a user-defined block):
extension NSViewController {
func descendantViewControllerPassingTest(test: (viewController: NSViewController) -> Bool) -> NSViewController? {
var retval: NSViewController?
for childViewController in childViewControllers {
if test(viewController: childViewController) {
retval = childViewController
} else if let descendantViewController = viewController.descendantViewControllerPassingTest(test) {
retval = descendantViewController
}
if retval != nil { break }
}
return retval
}
}
Ninety-nine percent of the time my tests consist of a simple type-check...
contentViewController.descendantViewControllerPassingTest {$0 is OutlineViewController}
...so I'd like to create a short convenience extension that does this:
extension NSViewController {
func descendantViewControllerMatchingType(type: WHAT_HERE?) {
return descendantViewControllerPassingTest({ $0 is WHAT_HERE? })
}
}
But I can't work out what type my parameter type should be. I've tried AnyClass and AnyObject but the compiler informs me that these aren't types:
Closures is what I'd use. Pass in your object to the closure with a return type of BOOL. Use the closure to match your descendant recursively.

Checking if a Swift class conforms to a protocol and implements an optional function?

I'm writing a helper function in Swift for use in SpriteKit games that will check if the collision detention has been set up correctly.
I want to check that my GameScene implements the protocol SKPhysicsContactDeletegate and also contains the didBeginContact function.
I have the following code, written with the help of Xcode's auto-complete:
if GameScene(conformsToProtocol(SKPhysicsContactDelegate)) {
if GameScene(respondsToSelector(didBeginContact(SKPhysicsContact))) {
print("Self implements SKPhysicsContactDelegate and didBeginContact appears to be present")
}
}
Xcode then complains about my conformsToProtocol, complaining that
'Argument labels '(_:)' do not match any available overloads'
with no suggestion how to fix. It also objects to respondsToSelector, stating that
'Cannot convert value of type '(SKPhysicsContact).Type' (aka
'SKPhysicsContact.Type') to expected argument type 'SKPhysicsContact'
How can I check if my GameScene conforms to this protocol and also implements this function?
Edit: Here's my code based upon the answers:
if GameScene.self is SKPhysicsContactDelegate {
print("Yes it's a delegate")
}
Output: Yes it's a delegate
let yy = (GameScene.self as? SKPhysicsContactDelegate)?.didBeginContact
print("yy is \(yy)")
Output: yy is nil
if (GameScene.self as? SKPhysicsContactDelegate)?.didBeginContact != nil {
print("Yes it's a delegate and the function is there")
}
No output.
You are still thinking in Objective-C, embrace Swift!
Assuming that your protocol looks like this:
#objc protocol SKPhysicsContactDelegate {
optional func didBeginContact()
}
Try this:
if let delegate = gameScene as? SKPhysicsContactDelegate {
delegate.didBeginContact?()
}
Or a one liner:
(gameScene as? SKPhysicsContactDelegate)?.didBeginContact?()
Notice the ? after the method name in the call? It's because that method is optional and it won't get called if the object doesn't implement that method. And the if let branch won't get executed if the object doesn't conforms to SKPhysicsContactDeletegate protocol.
Check method existence without call
To check the existence of the method itself before calling, just omit the method call to get a reference to that methodand check it like any other variable:
if let method = (gameScene as? SKPhysicsContactDelegate)?.didBeginContact {
print("gameScene conforms to SKPhysicsContactDelegate and implements didBeginContact")
// Call it later whenever you want
method()
}
If you don't need to call it later, just check for nil:
if (gameScene as? SKPhysicsContactDelegate)?.didBeginContact != nil {
print("gameScene conforms to SKPhysicsContactDelegate and implements didBeginContact")
}
Check for static methods
Checking for optional static methods uses the same approach, but requires the class object instead of an instance of the class:
if (GameScene.self as? OptionalProtocol.Type)?.staticMethod != nil {
print("gameScene conforms to OptionalProtocol and implements staticMethod")
}
Notice GameScene.self for obtaining the object type and <protocol>.Type to cast to the protocol class instead of a protocol instance.
Full sample
Attached full sample for Playgrounds, Swift script or any online Swift compiler:
import Foundation
#objc protocol OptionalProtocol {
optional func instanceMethod()
optional static func staticMethod()
}
class Nothing {}
class Something: OptionalProtocol {}
class Bar: NSObject, OptionalProtocol {
func instanceMethod() {
print("Instance method")
}
}
class Foo: NSObject, OptionalProtocol {
static func staticMethod() {
print("Static method")
}
}
// Cast instances to 'Any' and classes to 'AnyClass'
let nothing: Any = Nothing()
let nothingClass: AnyClass = Nothing.self
let something: Any = Something()
let somethingClass: AnyClass = Something.self
let bar: Any = Bar()
let barClass: AnyClass = Bar.self
let foo: Any = Foo()
let fooClass: AnyClass = Foo.self
nothing is OptionalProtocol // false
(nothing as? OptionalProtocol)?.instanceMethod != nil // false
(nothing as? OptionalProtocol)?.instanceMethod?() // Does nothing
(nothingClass as? OptionalProtocol.Type)?.staticMethod != nil // false
(nothingClass as? OptionalProtocol.Type)?.staticMethod?() != nil // Does nothing
something is OptionalProtocol // true
(something as? OptionalProtocol)?.instanceMethod != nil // false
(something as? OptionalProtocol)?.instanceMethod?() // Does nothing
(somethingClass as? OptionalProtocol.Type)?.staticMethod != nil // false
(somethingClass as? OptionalProtocol.Type)?.staticMethod?() != nil // Does nothing
bar is OptionalProtocol // true
(bar as? OptionalProtocol)?.instanceMethod != nil // true
(bar as? OptionalProtocol)?.instanceMethod?() // Prints 'Instance method'
(barClass as? OptionalProtocol.Type)?.staticMethod != nil // false
(barClass as? OptionalProtocol.Type)?.staticMethod?() != nil // Does nothing
foo is OptionalProtocol // true
(foo as? OptionalProtocol)?.instanceMethod != nil // false
(foo as? OptionalProtocol)?.instanceMethod?() // Does nothing
(fooClass as? OptionalProtocol.Type)?.staticMethod != nil // true
(fooClass as? OptionalProtocol.Type)?.staticMethod?() != nil // Prints 'Static method'
respondsToSelector fails, because it expects an instance of SKPhysicsContact and not SKPhysicsContact.type.
To check if an object implements an interface, you can use is.
So for example:
protocol Test {
func foo();
}
class TestImpl : Test {
func foo() {
print("bar")
}
}
let a = TestImpl()
let b = String()
print(a is Test) // true
print(b is Test) // false

Swift #objc protocol - distinguish optional methods with similar signature

Let's say we have a protocol in Swift:
#objc protocol FancyViewDelegate {
optional func fancyView(view: FancyView, didSelectSegmentAtIndex index: Int)
optional func fancyView(view: FancyView, shouldHighlightSegmentAtIndex index: Int) -> Bool
}
Note that both methods are optional and have the same prefix signature.
Now our FancyView class looks like this:
class FancyView: UIView {
var delegate: FancyViewDelegate?
private func somethingHappened() {
guard let delegateImpl = delegate?.fancyView else {
return
}
let idx = doALotOfWorkToFindTheIndex()
delegateImpl(self, idx)
}
}
The compiler jumps in our face:
We could change somethingHappened() to this:
private func somethingHappened() {
let idx = doALotOfWorkToFindTheIndex()
delegate?.fancyView?(self, didSelectSegmentAtIndex: idx)
}
However, as you can see we risk doing a lot of work only to throw away the index afterwards, because the delegate does not implement the optional method.
The question is: How do we if let or guard let bind the implementation of two optional methods with a similar prefix signature.
First, your objective C protocol needs to confirm to NSObjectProtocol to ensure we can introspect if it supports a given method.
Then when we want to call specific method, check if that method is supported by conforming object and if yes, then perform necessary computations needed to call that method. I tried this code for instance-
#objc protocol FancyViewDelegate : NSObjectProtocol {
optional func fancyView(view: UIView, didSelectSegmentAtIndex index: Int)
optional func fancyView(view: UIView, shouldHighlightSegmentAtIndex index: Int) -> Bool
}
class FancyView: UIView {
var delegate: FancyViewDelegate?
private func somethingHappened() {
if delegate?.respondsToSelector("fancyView:didSelectSegmentAtIndex") == true {
let idx :Int = 0 //Compute the index here
delegate?.fancyView!(self, didSelectSegmentAtIndex: idx)
}
}
}

nil is not convertible to hashable.

I have a custom control that is using a datasource to fetch items (as an NSTableView would do). The datasource can return Any-type, as long as it's hashable. The items are used as a key in a private dictionary.
The control (custom view) is added to the UI in interface builder.
I run into problems when I am querying the datasource with a nil parameter because nil is not convertible to hashable.
What is the proper way to do this?
protocol DataSourceProtocol
{
func numberOfChildrenOfItem<Item: Hashable>(item: Item?) -> Int
func child<Item: Hashable>(index: Int, ofItem item: Item?) -> Item
}
class MyControl : NSControl
{
var dataSource : DataSourceProtocol!
func reloadData()
{
//using string as an example of a hashable
let countA = dataSource.numberOfChildrenOfItem("item") // ok
let countB = dataSource.numberOfChildrenOfItem(nil) // not ok
let childA = dataSource.child(0, ofItem: "item") //ok
let childB = dataSource.child(0, ofItem: nil) //not ok
self.reloadChildren(childA)
self.reloadChildren(childB)
}
func reloadChildren<Item: Hashable>(item: Item)
{}
}
Use NSNull() to get a null object, which you can then compare to another NSNull() to see if its empty or not.