Add parameters to addTarget valueChanged action in Swift using composition - ios

First, let me state that I am not looking for a solution that is stated here. This one solves it using inheritance, but I cannot use that, as my needs are to have a composition solution.
I basically want to send more parameters when doing addTarget to a UIControl, using composition. I actually already have something working, but I am not sure at all if it's good practice, what are the drawbacks, and if there is another solution that is simpler. I'm hoping you can help me with that. Here is the solution. I basically wrap the control in a class that takes in the extra params and a closure that is called when the value changes.
class ControlWithParameters {
var control: UIControl
var extraParam: String
var valueChanged: (UIControl, String) -> ()
required init(_ control: UIControl, _ extraParam: String, _ valueChanged: #escaping (UIControl, String) -> ()) {
self.control = control
self.extraParam = extraParam
self.valueChanged = valueChanged
control.addTarget(self, action: #selector(valueChanged(sender:)), for: .valueChanged)
}
#objc internal func valueChanged(sender: UIControl) {
self.valueChanged(sender, self.extraParam)
}
}
let slider = UISlider()
let extraParam = "extraParam"
let controlWithParameters = ControlWithParameters(slider, extraParam, valueChanged)
func valueChanged(_ control:UIControl, _ extraParam: String) {
print(extraParam)
}
Is this a good solution? I can't know if this memory efficient or not, and I don't know if I'm complicating myself for this simple task. Any help is appreciated!

Related

Extension that makes wrapper that dispenses self typed as self

Sorry for the weird title. Here's a toy sketch of my code:
extension UIControl {
func makeHolder() -> ControlHolder {
ControlHolder(control: self)
}
}
struct ControlHolder {
let control : UIControl
init(control: UIControl) {
self.control = control
}
func retrieve() -> UIControl {
return self.control
}
}
I admit this is a toy reduction, and I don't like when people do that, but it illustrates the syntactical issue perfectly, so let's go with it.
Okay, so we have an extension on UIControl that is a method that returns a wrapper object. Now, the problem is this:
let holder = UISwitch().makeHolder()
let output = holder.retrieve()
The result, output, is typed as UIControl, obviously. But that isn't what I want. I want it to be typed as UISwitch, because I started with a UISwitch. OK, so that sounds like a generic. The problem is, I can't figure out how to make that generic.
It's easy, I think, to make ControlHolder a generic:
struct ControlHolder<T:UIControl> {
let control : T
init(control: T) {
self.control = control
}
func retrieve() -> T {
return self.control
}
}
I'm pretty sure I've got that part right. But then how do I write the extension declaration in such a way as to resolve that generic to the actual type of self, the UIControl on which makeHolder is called?
I tried introducing a generic to the extension, obeying the compiler until I got it to compile:
extension UIControl {
func makeHolder<T>() -> ControlHolder<T> {
ControlHolder<T>(control: self as! T)
}
}
But that's pretty silly, and output is still typed as UIControl.
Obviously I can add another parameter passing the type explicitly into makeHolder and thus resolving it:
extension UIControl {
func makeHolder<T>(ofType: T.Type) -> ControlHolder<T> {
ControlHolder<T>(control: self as! T)
}
}
Now when I call makeHolder I pass in the type:
let holder = UISwitch().makeHolder(ofType: UISwitch.self)
let output = holder.retrieve()
And now of course output is typed as UISwitch. But this is idiotic! I want the extension to just know that the type is UISwitch, because I'm calling makeHolder on a UISwitch.
I feel like I'm coming at this all wrong. Maybe someone can straighten me out? Or am I aiming for something that's just impossible?
The trick to this is to define a protocol, an extension of that protocol, and put the makeHolder method in that extension. That way, you can use Self as the generic type for the returned ControlHolder.
First define a new protocol (let's call it "HoldableControl") and require that conformers must be UIControls. It doesn't need any other requirements because we just care about adding the makeHolder function to an extension.
protocol HoldableControl: UIControl {}
Then, add an extension of HoldableControl and define makeHolder in it, returning ControlHolder<Self>. We are allowed to use Self here because it is allowed in protocol extensions, unlike in an extension to UIControl.
extension HoldableControl {
func makeHolder() -> ControlHolder<Self> {
ControlHolder(control: self)
}
}
Then, we just need to have UIControl conform to this protocol:
extension UIControl: HoldableControl {}
And make your ControlHolder generic, as you've already done:
struct ControlHolder<T: UIControl> {
let control: T
init(control: T) {
self.control = control
}
func retrieve() -> T {
control
}
}
And now it will work:
let holder = UISwitch().makeHolder() // type is ControlHolder<UISwitch>
let output = holder.retrieve() // type is UISwitch

Selectors in Swift 4.0

I was wondering if there is a more 'swift 4' way of creating a selector and calling a function? I am wanting to have the click of the Status Bar Button to call a simple print command from a function, but is this outdated or is there a more efficient 'swift' way of doing this?
button.action = #selector(myFunction)
#objc func myFunction (sender: NSStatusBarButton) {
print("Hi")
}
I am not sure if there's any good way to avoid using the target/action pattern under the hood, but you can definitely try to hide it.
Personally I use ReactiveSwift for all callbacks so I never have to use this awkward objc syntax. Another way to do it would be to hide this inside an extension. For instance, you can try something like:
extension UIButton {
private struct AssociatedKeys {
static var TouchUpClosure = "touchUpClosure"
}
internal var onTouchUpInside: ((UIButton) -> ())? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.TouchUpClosure) as? (UIButton) -> ()
}
set {
objc_setAssociatedObject(
self,
&AssociatedKeys.TouchUpClosure,
newValue as? (UIButton) -> (), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
button.action = #selector(executeTouchUpInside:)
}
}
#objc func executeTouchUpInside(sender: UIButton) {
self.touchUpInside(sender)
}
}
Which allows you to use a "more swift" syntax (no #objc or #selector):
button.onTouchUpInside = { _ in print("Hi") }
Disclaimer - I haven't checked if this exact code compiles, this post is more about sharing an idea.

Swift Xcode getting <<error type>> from generic type parameter inside closure

Swift's way to create a red button:
let button: UIButton = {
let button = UIButton()
button.backgroundColor = .red
return button
}()
With my extension (kotlin's approach):
let button: UIButton = UIButton().apply(){ this in
this.backgroundColor = UIColor.red
}
There are two problems with my extension, first one is that Xcode doesn't recognize the closure parameter "this", it shows << error type >>, so I don't have autocomplete (although it's working), and the second one is that I would like to be able to infer the generic type since I'm already instantiating it.
The extension:
extension NSObject {
func apply<T>(_ closure: (T) -> ()) -> T {
let this = self as! T
closure(this)
return this
}
}
I hope someone can find a solution for this. Thanks in advance.
The problem here is that Swift cannot infer the type of T. You did not and cannot tell Swift to use the current type as the type of the closure. Self is only available in the result type of methods in a class.
You can do something like this:
func apply<T>(_ obj: T, closure: (T) -> Void) -> T {
closure(obj)
return obj
}
apply(UIButton()) { this in this.backgroundColor = .red }
but I don't think that's what you want. You want the word apply to be in the middle, right?
Unfortunately, I cannot think of how you would achieve that. As an alternative, you can try replacing the word "apply" with an operator. For example, I think --> is quite suitable for this.
infix operator -->
func --> <T>(obj: T, closure: (T) -> Void) -> T {
closure(obj)
return obj
}
let button = UIButton() --> { this in this.backgroundColor = .red }
I'm not sure why it's not working for you. If the extension is inside a class put it outside of the class so it's on its own. If that is how it is already try clearing the errors with the command (command + k), this will reload them. If the error remains it's very confusing because it is working fine for me.

Passing arguments to selector in Swift

I'm programmatically adding a UITapGestureRecognizer to one of my views:
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(modelObj:myModelObj)))
self.imageView.addGestureRecognizer(gesture)
func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
The first problem I encountered was "Argument of '#selector' does not refer to an '#Objc' method, property, or initializer.
Cool, so I added #objc to the handleTap signature:
#objc func handleTap(modelObj: Model) {
// Doing stuff with model object here
}
Now I'm getting the error "Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C.
It's just an image of the map of a building, with some pin images indicating the location of points of interest. When the user taps one of these pins I'd like to know which point of interest they tapped, and I have a model object which describes these points of interest. I use this model object to give the pin image it's coordinates on the map so I thought it would have been easy for me to just send the object to the gesture handler.
It looks like you're misunderstanding a couple of things.
When using target/action, the function signature has to have a certain form…
func doSomething()
or
func doSomething(sender: Any)
or
func doSomething(sender: Any, forEvent event: UIEvent)
where…
The sender parameter is the control object sending the action message.
In your case, the sender is the UITapGestureRecognizer
Also, #selector() should contain the func signature, and does NOT include passed parameters. So for…
func handleTap(sender: UIGestureRecognizer) {
}
you should have…
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
Assuming the func and the gesture are within a view controller, of which modelObj is a property / ivar, there's no need to pass it with the gesture recogniser, you can just refer to it in handleTap
Step 1: create the custom object of the sender.
step 2: add properties you want to change in that a custom object of the sender
step 3: typecast the sender in receiving function to a custom object and access those properties
For eg:
on click of the button if you want to send the string or any custom object then
step 1: create
class CustomButton : UIButton {
var name : String = ""
var customObject : Any? = nil
var customObject2 : Any? = nil
convenience init(name: String, object: Any) {
self.init()
self.name = name
self.customObject = object
}
}
step 2-a: set the custom class in the storyboard as well
step 2-b: Create IBOutlet of that button with a custom class as follows
#IBOutlet weak var btnFullRemote: CustomButton!
step 3: add properties you want to change in that a custom object of the sender
btnFullRemote.name = "Nik"
btnFullRemote.customObject = customObject
btnFullRemote.customObject2 = customObject2
btnFullRemote.addTarget(self, action: #selector(self.btnFullRemote(_:)), for: .touchUpInside)
step 4: typecast the sender in receiving function to a custom object and access those properties
#objc public func btnFullRemote(_ sender: Any) {
var name : String = (sender as! CustomButton).name as? String
var customObject : customObject = (sender as! CustomButton).customObject as? customObject
var customObject2 : customObject2 = (sender as! CustomButton).customObject2 as? customObject2
}
Swift 5.0 iOS 13
I concur a great answer by Ninad. Here is my 2 cents, the same and yet different technique; a minimal version.
Create a custom class, throw a enum to keep/make the code as maintainable as possible.
enum Vs: String {
case pulse = "pulse"
case precision = "precision"
}
class customTap: UITapGestureRecognizer {
var cutomTag: String?
}
Use it, making sure you set the custom variable into the bargin. Using a simple label here, note the last line, important labels are not normally interactive.
let precisionTap = customTap(target: self, action: #selector(VC.actionB(sender:)))
precisionTap.customTag = Vs.precision.rawValue
precisionLabel.addGestureRecognizer(precisionTap)
precisionLabel.isUserInteractionEnabled = true
And setup the action using it, note I wanted to use the pure enum, but it isn't supported by Objective C, so we go with a basic type, String in this case.
#objc func actionB(sender: Any) {
// important to cast your sender to your cuatom class so you can extract your special setting.
let tag = customTag as? customTap
switch tag?.sender {
case Vs.pulse.rawValue:
// code
case Vs.precision.rawValue:
// code
default:
break
}
}
And there you have it.
cell.btn.tag = indexPath.row //setting tag
cell.btn.addTarget(self, action: #selector(showAlert(_ :)), for: .touchUpInside)
#objc func showAlert(_ sender: UIButton){
print("sender.tag is : \(sender.tag)")// getting tag's value
}
Just create a custom class of UITapGestureRecognizer =>
import UIKit
class OtherUserProfileTapGestureRecognizer: UITapGestureRecognizer {
let userModel: OtherUserModel
init(target: AnyObject, action: Selector, userModel: OtherUserModel) {
self.userModel = userModel
super.init(target: target, action: action)
}
}
And then create UIImageView extension =>
import UIKit
extension UIImageView {
func gotoOtherUserProfile(otherUserModel: OtherUserModel) {
isUserInteractionEnabled = true
let gestureRecognizer = OtherUserProfileTapGestureRecognizer(target: self, action: #selector(self.didTapOtherUserImage(_:)), otherUserModel: otherUserModel)
addGestureRecognizer(gestureRecognizer)
}
#objc internal func didTapOtherUserImage(_ recognizer: OtherUserProfileTapGestureRecognizer) {
Router.shared.gotoOtherUserProfile(otherUserModel: recognizer.otherUserModel)
}
}
Now use it like =>
self.userImageView.gotoOtherUserProfile(otherUserModel: OtherUserModel)
You can use an UIAction instead:
self.imageView.addAction(UIAction(identifier: UIAction.Identifier("imageClick")) { [weak self] action in
self?.handleTap(modelObj)
}, for: .touchUpInside)
that may be a terrible practice but I simply add whatever I want to restore to
button.restorationIdentifier = urlString
and
#objc func openRelatedFact(_ sender: Any) {
if let button = sender as? UIButton, let stringURL = factButton.restorationIdentifier, let url = URL(string: stringURL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
}

Swift: Accept a closure with varying parameters

Suppose I have a function that accepts a callback with a sender, like this:
func performAction(aNumber: Double, completion: (sender: UIButton) -> Void) {
// Does some stuff here
let button = getAButtonFromSomewhere()
completion(button)
}
And so one possible way to call this function is by passing an existing function for the callback, rather than defining the closure in-place:
performAction(10, completion: myCallback)
func myCallback(sender: UIButton) {
sender.setTitle("foo", forState: .Normal)
}
Back in my definition for performAction, how can I define the completion block to accept a UIButton or any subclass of it?
As an example, suppose I have a UIButton subclass called CustomButton. So in my callback, I'm only interested in accepting a CustomButton. I'd like to do this:
performAction(10, completion: myCallback)
// This produces a compiler error:
func myCallback(sender: CustomButton) {
sender.setTitle("foo", forState: .Normal)
}
// This works, but forces me to cast to my custom class:
func myCallback(sender: UIButton) {
let realButton = sender as! CustomButton
realButton.setTitle("foo", forState: .Normal)
}
But the compiler won't allow it, because the definition of performAction requires the callback to accept a UIButton specifically (even though CustomButton is a UIButton subclass).
I'd like performAction to be generic so that it can be packaged in a library, and work with any UIButton subclass. Is this possible to do in Swift?
EDIT: I tried to simplify what I'm doing with the example above, but I think it just caused confusion. Here's the actual code that I'm trying to make work, with some improvements thanks to #luk2302:
public extension UIButton {
private class Action: AnyObject {
private var function: Any
init(function: Any) {
self.function = function
}
}
// Trickery to add a stored property to UIButton...
private static var actionsAssocKey: UInt8 = 0
private var action: Action? {
get {
return objc_getAssociatedObject(self, &UIButton.actionsAssocKey) as? Action
}
set(newValue) {
objc_setAssociatedObject(self, &UIButton.actionsAssocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
internal func performAction(sender: UIButton) {
if let function = self.action!.function as? () -> Void {
function()
// THIS IS WHERE THINGS BREAK NOW:
} else if let function = self.action!.function as? (sender: self.Type) -> Void {
function(sender: self)
}
}
public func addTarget(forControlEvents event: UIControlEvents, action: () -> Void) {
self.action = Action(function: action)
self.addTarget(self, action: "performAction:", forControlEvents: event)
}
public func addTarget<B: UIButton>(forControlEvents: UIControlEvents, actionWithSender: (sender: B) -> Void) {
self.action = Action(function: actionWithSender)
self.addTarget(self, action: "performAction:", forControlEvents: forControlEvents)
}
}
The only piece that breaks now is the line that I commented, at (sender: self.Type) (self being either UIButton, or some subclass of it).
So this deviates from my original question slightly, but how can I can I cast function to a closure accepting a sender of the same type as self? This code works perfectly if I hard-code the type, but it should be able to work for any UIButton subclass.
You can make the UIButton subclass a generic parameter for the performAction function, but then you will need to cast the button before passing it to the callback, unless you also have a generic way of "getting" the right type of button.
// performAction() works with any type of UIButton
func performAction<B: UIButton>(aNumber: Double, completion: (sender: B) -> Void)
{
// Assuming getAButtonFromSomewhere returns UIButton, and not B, you must cast it.
if let button = getAButtonFromSomewhere() as? B {
completion(sender: button)
}
}
Ask yourself this: what should happen if you pass a closure of type (CustomButton -> Void) as completion and then getAButtonFromSomewhere returns an instance of UIButton? The code then cannot invoke the closure since the UIButton is not a CustomButton.
The compiler simply does not allow you to pass (CustomButton -> Void) to (UIButton -> Void) because (CustomButton -> Void) is more restrictive than (UIButton -> Void). Note that you can pass (UIButton -> Void) to a closure of type (CustomButton -> Void) since (UIButton -> Void) is less restrictive - you can pass everything you pass to the first to the second as well.
Therefore either make func use a generic type as #jtbandes suggests or use your initial approach a little bit improved:
func myCallback(sender: UIButton) {
if let realButton = sender as? CustomButton {
realButton.setTitle("foo", forState: .Normal)
}
}
Both solutions will result in the setTitle to not be invoked whenever the returned value of getAButtonFromSomewhere is not a CustomButton.

Resources