Swift 函数@IBAction的参数是什么?为什么我可以用这种方式添加层动画

Swift 函数@IBAction的参数是什么?为什么我可以用这种方式添加层动画,swift,Swift,在函数中,我可以得到一个参数,它是UISlider,但它的参数类型是CALayer。编译器不会抛出错误,它是不可删除的。我不知道该怎么做,但它很有效 @IBOutlet weak var vectorSlider: UISlider! override func viewDidLoad() { super.viewDidLoad() vectorSlider.addTarget(nil, action: #selector(changeVector), for: UIContr

在函数中,我可以得到一个参数,它是
UISlider
,但它的参数类型是
CALayer
。编译器不会抛出错误,它是不可删除的。我不知道该怎么做,但它很有效

@IBOutlet weak var vectorSlider: UISlider!

override func viewDidLoad() {
    super.viewDidLoad()
    vectorSlider.addTarget(nil, action: #selector(changeVector), for: UIControl.Event.valueChanged) // Less than Swift5,UIControlEvents.valueChanged
}

@IBAction func changeVector(layer: CALayer) {
    let transformAnimaZ = CABasicAnimation.init(keyPath: "transform.rotation.z")
    transformAnimaZ.fromValue = vectorSlider.value
    transformAnimaZ.toValue = vectorSlider.value
    let transformGroup = CAAnimationGroup()
    transformGroup.animations = [transformAnimaZ]
    transformGroup.isRemovedOnCompletion = false
    transformGroup.fillMode = CAMediaTimingFillMode.forwards // Less than Swift5,kCAFillModeForwards
    layer.add(transformGroup, forKey: "transform")
}

我想知道如何获取参数。我只找到了一些关于
UIControl
的答案,而不是
CALayer
。为什么我可以用这种方式添加图层动画。

这将有助于阶段性地考虑这件事。让我们从标准模式开始(在视图控制器中):

这里发生的事情是,我们走出了Swift,进入了Cocoa的Objective-C世界。在
#selector(action)
中,我们所做的只是形成一个字符串——在本例中是
“actionWithSender:”
,但细节是不相关的。Cocoa现在知道,如果点击按钮,它将向我们发送消息
“actionWithSender:”
。有一个参数,所以Cocoa将传递给发送方,即按钮。这就是当动作方法有一个参数时,Cocoa在控制目标动作机制方面的行为方式;你对此无能为力,也无法改变

现在,让我们更改参数类型的Swift声明:

@IBOutlet var myButton : UIButton!
override func viewDidLoad() {
    super.viewDidLoad()
    self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
@objc func action(layer:CALayer) {
    print("here")
}
就Objective-C而言,这并没有改变什么。我们将函数名更改为
“actionWithLayer:”
,但Objective-C对参数的类型一无所知(“CALayer”部分)。它只是直接调用方法,然后传递发送方,发送方仍然是按钮。您可以通过打印
参数来证明:

@IBOutlet var myButton : UIButton!
override func viewDidLoad() {
    super.viewDidLoad()
    self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
@objc func action(layer:CALayer) {
    print(layer) // UIButton...
}
所以现在你对斯威夫特撒了谎,说什么样的对象将作为我们的
动作
方法的参数到达。但是你的谎言并不重要,因为除了打印它,你没有对这个参数做任何事情。好的,但是现在让我们更改
层的
变换

@IBOutlet var myButton : UIButton!
override func viewDidLoad() {
    super.viewDidLoad()
    self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
@objc func action(layer:CALayer) {
    print(layer) // UIButton...
    layer.transform = CATransform3DMakeRotation(.pi, 0, 0, 1)
}
Swift允许我们这样说,因为CALayer有一个
变换
,它是一个CATTransformM3D。但只有一个问题。这实际上不是一个加莱尔;它是一个UIView(本例中是一个按钮)。按钮有一个层,但它不是CATTransform3D(而是仿射变换)。因此,Swift允许该消息,但它是无效的

@IBOutlet weak var vectorSlider: UISlider!

override func viewDidLoad() {
    super.viewDidLoad()
    vectorSlider.addTarget(nil, action: #selector(changeVector), for: UIControl.Event.valueChanged) // Less than Swift5,UIControlEvents.valueChanged
}

@IBAction func changeVector(layer: CALayer) {
    let transformAnimaZ = CABasicAnimation.init(keyPath: "transform.rotation.z")
    transformAnimaZ.fromValue = vectorSlider.value
    transformAnimaZ.toValue = vectorSlider.value
    let transformGroup = CAAnimationGroup()
    transformGroup.animations = [transformAnimaZ]
    transformGroup.isRemovedOnCompletion = false
    transformGroup.fillMode = CAMediaTimingFillMode.forwards // Less than Swift5,kCAFillModeForwards
    layer.add(transformGroup, forKey: "transform")
}
也许我们没有崩溃是令人惊讶的,但这可能是因为一个按钮实际上首先有一个
转换。在摆脱了强打字之后,我们能够做一些不连贯的事情并侥幸逃脱。但如果你这么说,你会崩溃的:

@IBOutlet var myButton : UIButton!
override func viewDidLoad() {
    super.viewDidLoad()
    self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
@objc func action(layer:CALayer) {
    print(layer) // UIButton...
    layer.zPosition = 0
}
基本上你骗了斯威夫特(这个东西是一个按钮,不是一个计算器),所以我们可以试着设置它的
zPosition
,但是在运行时它变成了一个按钮,没有
zPosition
,我们崩溃了

这有什么寓意?不要那样做!你在对斯威夫特撒谎,因此去掉了斯威夫特最好的特征,即它强大的类型检查能力。相反,请始终键入您的
发件人
,并安全地放下。如果你搞错了,演员会阻止你:

@IBOutlet var myButton : UIButton!
override func viewDidLoad() {
    super.viewDidLoad()
    self.myButton.addTarget(self, action: #selector(action), for: .touchUpInside)
}
@objc func action(sender:Any) {
    if let layer = sender as? CALayer {
        layer.zPosition = 0
    } else {
        print("it's not a layer you silly person")
    }
}

编辑在问题的第二个版本中,您已将
add(uwy:forKey:)
发送到滑块,它的工作方式就像您已将其发送到图层一样。但滑块不是一个层。发生的事情是,您发现了一个秘密:在幕后,在Objective-C/Cocoa中,UIView实际上响应了
addAnimation:forKey:
,我们可以通过使用符号断点探测Cocoa看到:

我们可以通过
响应
来发现同样的事实:

let slider = UIView()
(slider as AnyObject).responds(to: #selector(CALayer.add(_:forKey:))) // true!

UIView通过将该消息传递到其层来响应该消息。但这并没有改变现实:仅仅因为你说这是一个加莱尔,它不是!这是一个美丽的风景!!巧合的是,UIView实际上响应了这条消息,而您没有崩溃,事实上该层也有动画。

您的问题不清楚,请详细解释您不能回答的问题。iAction将发送方作为参数。它说它是一个CALayer,但实际上它不是必需的。代码可以运行。它很有效。尝试清理它并重新构建它。我已经仔细阅读了你的答案。很抱歉上传错误代码,我已经更新了它。我从你的回答中学到了很多。但这不能解决我的疑问。请尝试新代码,非常感谢!你只需要更仔细地阅读我的答案。不能使层到达控制操作处理程序。它将始终是一个控件,例如按钮或滑块。我证明了,现在你也证明了,我的结论和你一样,这就是为什么我不能理解它。控件在滑块改变值后会真正旋转,这与我在回答中给出的原因相同:你并不完全了解每个类的秘密特性。您已经通过了编译器,并将
add(\uu:forKey:)
发送到UIView。从某种意义上说,这是合法的,UIView实际上会响应这一点,将消息传递到它的层。但这是个秘密!你偷看了引擎盖下面,这就是发生的一切。没有造成伤害。但你并不是在神奇地将一个视图变成一个图层或类似的东西;(滑块作为AnyObject)。响应(到:#选择器(CALayer.add(3;:forKey:))
。它返回
真值
。所以这在Objective-C中是合法的,但它不是已发布API的一部分。你对编译器撒谎,这是在隐藏已发布的API。所以你发现了秘密。