Swift: How to use instanceType in callback closure - swift

I want to pass self as instancetype to the callback closure of this function:
extension UIView {
public func onTap(_ handler: #escaping (_ gesture: UITapGestureRecognizer, _ view: Self) -> Void) -> UITapGestureRecognizer {
...
}
}
let view = UIView.init()
view.onTap { tap, v in
...
}
But I got an error:
Self' is only available in a protocol or as the result of a method in a class; did you mean 'UIView'?
How can I do this?

that is just the perfect scenario (by book) when you can use protocols and extensions in Swift quite efficiently:
protocol Tappable { }
extension Tappable { // or alternatively: extension Tappable where Self: UIView {
func onTap(_ handler: #escaping (UITapGestureRecognizer, Self) -> Void) -> UITapGestureRecognizer {
return UITapGestureRecognizer() // as default to make this snippet sane
}
}
extension UIView: Tappable { }
then for e.g.:
let button = UIButton.init()
button.onTap { tap, v in
// v is UIButton...
}
while for e.g.:
let label = UILabel.init()
label.onTap { tap, v in
// v is UILabel...
}
etc...
NOTE: you can read more about Extensions or the Protocols in the Swift Programming Language Book from Apple.

Related

Execute closure from another method in an extension Swift

I have a class that is a delegate to a custom modal view, therefore it has a method for when the modal has been dismissed. I am extending that class and in the extension I have a method that accepts a completion closure.
I can't write in the main implementation of the class or the modal's implementation - only the extension.
I want to execute the closure when the modal is dismissed, but I can't seem to figure it out on my own. Is it even possible?
Here is an example of what I want to do:
extension MyClass {
func method(completion: (Int) -> ()) {
// showing the modal
}
}
extension MyClass: ModalDelegate {
func modalDismissed() {
// here I want to execute the completion passed to method()
}
}
Assign the completion as a variable to a MyClass's property in method and call it in modalDismissed:
class MyClass {
var classCompletion: (() -> ())?
}
extension MyClass {
func method(completion: #escaping () -> ()) {
classCompletion = completion
}
}
extension MyClass: ModalDelegate {
func modalDismissed() {
classCompletion?()
}
}
Update: If you can't "write in the main implementation of the class", here's a method using static property. This is a hack and has many limitations but does the work. Otherwise without writing to the main implementation you can't do anything.
extension MyClass {
static var classCompletion: (() -> ())?
func method(completion: #escaping () -> ()) {
MyClass.classCompletion = completion
}
}
extension MyClass: ModalDelegate {
func modalDismissed() {
MyClass.classCompletion?()
}
}

Self type as argument in closure

Is it possible for an instance of a UIView to call a method which executes a closure, and inside that closure referring to the same instance? This is the non-generic version:
import UIKit
public extension UIView {
func layout(from: (UIView) -> ()) {
from(self)
}
}
When I call it with a UILabel for example, I do not have access to e.g. the text aligment. Is it possible that inside the closure I can refer to the UILabel? I would expect something like this would work:
func layout(from: (Self) -> ()) {
from(self)
}
But it doesn't compile. Is there a workaround? This is what I want:
let label = UILabel(frame: .zero)
label.layout { $0.textAlignment = .natural } // Currenly not working, since $0 = UIView.
Different approach: Protocol Extension with associated type.
protocol Layout {
associatedtype View : UIView = Self
func layout(from: (View) -> ())
}
extension Layout where Self : UIView {
func layout(from: (Self) -> ()) {
from(self)
}
}
extension UIView : Layout {}
let label = UILabel(frame: .zero)
label.layout { $0.textAlignment = .natural }
There are different ways to do it.
Firstly, you could use the closures' variable capturing system in order to directly use the variable inside the closure, without passing it as an argument.
public extension UIView {
func layout(from: () -> ()) {
from()
}
}
label.layout { label.textAlignment = .natural }
Otherwise, if you want to pass a generic UIView and change the behaviour accordingly to the specific one - since it looks like you know for sure what type you are working on - you can use a downcast:
public extension UIView {
func layout(from: (UIView) -> ()) {
from(self)
}
}
let label = UILabel(frame: .zero)
label.layout { ($0 as! UILabel).textAlignment = .natural }
Anyway, why are you doing:
label.layout { $0.textAlignment = .natural }
instead of:
label.textAlignment = .natural
Is there any particular reason not to do it? I imagine there's something bigger behind the scenes, I'm just curious.

Creating a selector with variable of function type

I am working on two views that are subclassing subclass of UITableViewCell. In the base one (subclass of UITableViewCell) I am trying to setup gesture recognizer in a way that each of super class could change the behavior (eventually call didTapped method on it's delegate) of the tap.
I have written following code. I can use #selector(tap), however I think that using a variable instead of overriding a tap method in each super class is a much cleaner way. Is it even possible to use something like #selector(tapFunc)? If no what would be the cleanest and best from engineering point of view solution?
class BaseCell: UITableViewCell {
#objc var tapFunc: () -> () = { () in
print("Tapped")
}
#objc func tap() {
print("TEST")
}
func setupBasicViews(withContent: () -> ()) {
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(tapFunc))
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(tapGestureRecoginzer)
}
}
And then two views that are building on top of this one:
class ViewA: BaseCell {
//don't want to do this
override func tap() {
//do stuff
}
func setup {
//setup everything else
}
class ViewB: BaseCell {
var delegate: ViewBProtocool?
func setup {
tapFunc = { () in
delegate?.didTapped(self)
}
//setup everything else
}
You're not too far off. Make the following changes:
class BaseCell: UITableViewCell {
var tapFunc: (() -> Void)? = nil
// Called by tap gesture
#objc func tap() {
tapFunc?()
}
func setupBasicViews(withContent: () -> ()) {
let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(tap))
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(tapGestureRecoginzer)
}
}
class ViewA: BaseCell {
func setup() {
//setup everything else
}
}
class ViewB: BaseCell {
var delegate: ViewBProtocol?
func setup() {
tapFunc = {
delegate?.didTapped(self)
}
//setup everything else
}
}
Now each subclass can optionally provide a closure for the tapFunc property.
I show above that tapFunc is optional with no default functionality in the base class. Feel free to change that to provide some default functionality if desired.

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
}
}
}

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)
}
}
}