#Selector as a parameter? - ios

I want to create a function that adds a refreshControll to a UITableView.
The problem I have is the #selector:
extension UITableView{
func refreshTableView(callFunction: #selector()){
refreshControl = UIRefreshControl()
refreshControl?.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl?.addTarget(self, action: #selector(callFunction), for: .valueChanged)
self.addSubview(refreshControl!)
}
}
I want to specify which function to call when I use this function.
Is this even possible?

#selector(...) is an expression of type Selector, so your method should accept a Selector object and pass it directly to the action: parameter.
extension UITableView {
func refreshTableView(callFunction: Selector) {
// ...
refreshControl?.addTarget(self, action: callFunction, for: .valueChanged)
// ...
}
}
myTableView.refreshTableView(callFunction: #selector(whatever))

Related

Swift: Protocol method as Action in target-action

I'd like to have a protocol:
protocol CameraButtonDelegate: class {
func cameraButtonDidPress(_ sender: UIButton)
}
So, I could assign any client to a button, e.g.:
cameraButton.addTarget(delegate, action: #selector(cameraButtonDidPress), for: .touchUpInside)
However, it does not compile as I have to specify a particular function in action, e.g.:
cameraButton.addTarget(delegate, action: #selector(AAPLViewController.cameraButtonDidPress), for: .touchUpInside)
How to solve this issue, if I'd like to have multiple clients being targeted by a single button?
It should work if you make the protocol #objc
#objc protocol CameraButtonDelegate: class {
func cameraButtonDidPress(_ sender: UIButton
}
and specify the selector as protocol method
cameraButtonDidPress.addTarget(delegate, action: #selector(CameraButtonDelegate.cameraButtonDidPress), for: .touchUpInside)
You can try
cameraButton.addTarget(Service.shared, action: #selector(Service.shared.cameraButtonDidPress(_:)), for: .touchUpInside)
//
class Service {
static let shared = Service()
#objc func cameraButtonDidPress (_ sender:UIButton){
}
}

Adding a target to button inside a closure doesn't work

The following code is located inside a subclass of UIView
I am setting up a cancelButton inside a closure:
private var cancelButtonClosure: UIButton = {
...
button.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
}()
And at first I instantiated the button inside a function like so:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
...
addSubview(cancelButton)
...
}
However this resulted in the cancel function not being called at all (even though the layout was right and the button was highlighting)
So I made these change:
Removed the addTarget part from the cancelButtonClosure
Added the addTarget part inside the showConfirmationView function
So it looked like that:
func showConfirmationView(...) {
...
let cancelButton = self.cancelButtonClosure
cancelButton.addTarget(self, action: #selector(cancel(_:)), for: .touchUpInside)
...
addSubview(cancelButton)
...
}
It worked: the cancel function was called; but I don't know why. I'm really curious to know why what I did before did not work. Thanks for your insights!
Check your implementation because a setup like this works as expected:
private var cancelButton: UIButton = {
let btn = UIButton(type: .system)
btn.setTitle("Cancel", for: .normal)
btn.addTarget(self, action: #selector(cancelSomething(_:)), for: .touchUpInside)
return btn
}()
#objc func cancelSomething(_ sender: UIButton) {
print("Something has to be cancelled")
}
override func viewDidLoad() {
super.viewDidLoad()
showConfirmationView()
}
func showConfirmationView() {
cancelButton.sizeToFit()
cancelButton.center = view.center
view.addSubview(cancelButton)
}

Dismissal Error

I have a setup that brings up a date picker upon clicking of a button. I have two a function that changes the value of a button upon selecting a date. In addition, I also have a function that should help dismiss the date picker when I press the done button. However, I continue to get an error stating 'unrecognized selector sent to instance'. Any idea what I could have possibly done wrong
#IBOutlet weak var dueDateSelector: UIButton!
#IBOutlet weak var myPickerView: UIPickerView!
#IBAction func changeDueDate(_ sender: Any) {
//Create the view
let inputView = UIView(frame: CGRect(x:0, y:420, width: self.view.frame.width, height: 240))
var datePickerView : UIDatePicker = UIDatePicker(frame: CGRect(x:0, y:40, width:0, height:0))
datePickerView.datePickerMode = UIDatePickerMode.date
inputView.addSubview(datePickerView) // add date picker to UIView
let doneButton = UIButton(frame: CGRect(x:270, y:0, width:100, height:50))
doneButton.setTitle("Done", for: UIControlState.normal)
doneButton.setTitle("Done", for: UIControlState.highlighted)
doneButton.setTitleColor(UIColor.black, for: UIControlState.normal)
doneButton.setTitleColor(UIColor.gray, for: UIControlState.highlighted)
inputView.addSubview(doneButton) // add Button to UIView
doneButton.addTarget(self, action: "doneButton:", for: UIControlEvents.touchUpInside) // set button click event
datePickerView.addTarget(self, action: Selector("handleDatePicker:"), for: UIControlEvents.valueChanged)
view.addSubview(inputView)
handleDatePicker(sender: datePickerView) // Set the date on start.
}
#objc func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
dueDateSelector.setTitle(dateFormatter.string(from: sender.date), for: .normal)
}
#objc func doneButton(sender:UIButton)
{
dueDateSelector.resignFirstResponder() // To resign the inputView on clicking done.
}
This is due to an inconsistency in naming methods between Swift and Objective C. This is also why you should write selectors with the #selector syntax instead of using strings. You need to change this line:
datePickerView.addTarget(self, action: Selector("handleDatePicker:"), for: UIControlEvents.valueChanged)
Selectors are an Objective C thing. If you write it in strings, then you need to write it in an Objective C fashion. In your code, handleDatePicker is not actually called handleDatePicker: in Objective C. It is probably something like handleDatePickerWithSender:. This is because you have a sender argument there, and that becomes the WithSender part in the eyes of Objective C.
This is why we like to write selectors like this:
#selector(handleDatePicker)
No need to worry about parameters, just the method name.
datePickerView.addTarget(self, action: #selector(handleDatePicker), for: UIControlEvents.valueChanged)
Also, this line needs to be changed as well:
doneButton.addTarget(self, action: "doneButton:", for: UIControlEvents.touchUpInside)
It should be:
doneButton.addTarget(self, action: #selector(doneButton), for: UIControlEvents.touchUpInside)
I think you are having problem here,
#objc func doneButton(sender:UIButton)
{
dueDateSelector.resignFirstResponder() // To resign the inputView on clicking done.
}
here dueDateSelector is button which is not having any method called resignFirstResponder() so that's why you are getting error
instead you can do this,
#objc func doneButton(sender:UIButton)
{
//remove or hide the input view
}
You can also check that whether current component having this method or not with,
if dueDateSelector.canResignFirstResponder()
{
}
You are using deprecated string selector syntax:
doneButton.addTarget(self, action: "doneButton:", for: UIControlEvents.touchUpInside)
Should be replaced with:
doneButton.addTarget(self, action: #selector(doneButton(sender:)),
for: .touchUpInside)
It's also applied to the other method in your code:
datePickerView.addTarget(self, action: #selector(handleDatePicker(sender:)),
for: .valueChanged)
doneButton.addTarget(self, action:#selector(self.doneButton(_:)) , for:
UIControlEvents.touchUpInside) // set button click event
datePickerView.addTarget(self, action: #selector(self.handleDatePicker(_:)), for: UIControlEvents.valueChanged)
if self wont work use class reference like yourClass().doneButton

isHighlighted called multiple times on UIButton

I have subclassed UIButton and want to call a delegate method just once when the button goes into the highlighted state and call it again just once when it goes into the unhighlighted state:
override var isHighlighted: Bool {
didSet {
if isHighlighted {
delegate?.buttonHighlightStateDidChange(highlighted: true)
} else {
delegate?.buttonHighlightStateDidChange(highlighted: false)
}
}
}
However when I touch down on the button it seems that didSet is getting repeatedly called. What am I doing wrong here? How can I call the delegate method just once?
I would recommend against using your subclass in this way. UIControl has a builtin mechanism for getting callbacks in response to control events:
func registerActions(for button: UIButton) {
button.addTarget(self, action: #selector(MyClass.buttonIsHighlighted(sender:)), for: .touchDown)
button.addTarget(self, action: #selector(MyClass.buttonIsUnHighlighted(sender:)), for: .touchUpInside)
button.addTarget(self, action: #selector(MyClass.buttonIsUnHighlighted(sender:)), for: .touchUpOutside)
}
func buttonIsHighlighted(sender: UIButton) {
// highlighted
}
func buttonIsUnHighlighted(sender: UIButton) {
// unhighlighted
}

Pull to refresh in Swift

I'm trying to implement pull to refresh in my table view app. I've been looking around at people's examples and I've gathered that this is pretty much the gist of it:
var refreshControl:UIRefreshControl!
override func viewDidLoad()
{
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl)
}
func refresh(sender:AnyObject)
{
// Code to refresh table view
}
However the only examples I can find are from a while ago and I know the language has changed a lot since then! When I try to use the code above, I get the following error next to my declaration of refreshControl:
Cannot override with a stored property 'refresh control'
My first thought before reading other examples is that I would have to declare the variable like so:
var refreshControl:UIRefreshControl = UIRefreshControl()
Like I did with some other variables but I guess not.
Any ideas what the issue is?
I gather your class inherits UITableViewController. UITableViewController already declares the refreshControl property like this:
#availability(iOS, introduced=6.0)
var refreshControl: UIRefreshControl?
You don't need to override it. Just get rid of your var declaration and assign to the inherited property.
Since the inherited property is Optional, you need to use a ? or ! to unwrap it:
refreshControl = UIRefreshControl()
refreshControl!.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
tableView.addSubview(refreshControl!)
Just add this code in viewDidLoad
self.refreshControl = UIRefreshControl()
self.refreshControl!.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refreshControl!)
Works Fine :)

Resources