This is my code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let btn = UIButton.init(frame: CGRect(x: 100, y: 100, width: 100, height: 100));
btn.backgroundColor = UIColor.blue
btn.addTarget(self, action: #selector(testAction(_:animated:)), for: .touchDown)
view.addSubview(btn)
}
@objc func testAction(_ sender: UIButton,animated: Bool = true){
if sender.backgroundColor == UIColor.red {
sender.backgroundColor = UIColor.blue
}else{
sender.backgroundColor = UIColor.red
}
print("animated == \(animated)")
}
}
As shown in the picture, a button is added to the test project. The method has default parameters, but the animated
parameter is printed as false
. Can anyone tell me why? I would be very grateful.
Is this a system bug? I have tested this in many scenarios.
The UIButton
calls testAction
by performing the selector you passed to action:
on the object you passed to target:
, using a method like performSelector:withObject:
. This is inherently an Objective-C thing, and Swift parameters' default values just don't work in Objective-C.
In Objective-C, func testAction(animated: Bool = true)
is imported as if it doesn't have an optional parameter. The header looks like this:
- (void)testActionWithAnimated:(BOOL)animated;
UIButton
expects its action
to be a method that takes a parameter representing the "sender" of the event, and it would be pass itself (an UIButton
instance) as the argument.
So what happens here is that the animated
parameter gets passed a pointer to an instance of UIButton
, which is forcefully interpreted as a BOOL
(because Objective-C has a rather weak type system). The result of that happens to be false
.
If you declare testAction
with two parameters,
@objc func testAction(_ sender: UIButton, animated: Bool = true)
UIButton
would pass a UIEvent
instance to the second parameter, and this again gets interpreted as a boolean, and the result happens to be false
.