- 小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
效果:
接下来为弹窗View在初始化的地方添加stackView,将stackView的填充方式设为填充满并平均分配,子views之间间距设为8,stackView里面的上下左右margin也设为8,并且添加了2个View作为stackView的子view,这样长按点击的时候方便看到是否添加成功。
let iconsContainerView:UIView = {
let view = UIView()
view.backgroundColor = .gray
let redView = UIView()
redView.backgroundColor = .red
let blueView = UIView()
blueView.backgroundColor = .blue
let arrangedSubviews = [redView,blueView]
let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
stackView.distribution = .fillEqually
let padding:CGFloat = 8
stackView.spacing = padding
stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
stackView.isLayoutMarginsRelativeArrangement = true
view.frame = CGRect(x: 0, y: 0, width: 200, height: 100)
stackView.frame = view.frame
view.addSubview(stackView)
return view
}()
接下来设定想要的icon的高度,然后根据高度算出需要的width,并将值赋值给弹窗View。
let iconHeight:CGFloat = 50
let numIcons = CGFloat(stackView.arrangedSubviews.count)
let width = numIcons * iconHeight + (numIcons + 1) * padding
view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)
接下来用高阶函数创建一个图片的array,并将其设为stackView的arrangedSubviews,为图片和弹窗View添加圆角,并为弹窗View添加阴影。
let iconsContainerView:UIView = {
let view = UIView()
view.backgroundColor = .gray
let iconHeight:CGFloat = 50
let images = [UIImage(named: "cry_laugh"),UIImage(named: "angry"),UIImage(named: "cry"),UIImage(named: "surprised"),UIImage(named: "blue_like"),UIImage(named: "red_heart")]
let arrangedSubviews = images.map { (image) -> UIView in
let imageView = UIImageView(image: image)
imageView.layer.cornerRadius = iconHeight / 2
imageView.isUserInteractionEnabled = true
return imageView
}
let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
stackView.distribution = .fillEqually
let padding:CGFloat = 8
stackView.spacing = padding
stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
stackView.isLayoutMarginsRelativeArrangement = true
let numIcons = CGFloat(stackView.arrangedSubviews.count)
let width = numIcons * iconHeight + (numIcons + 1) * padding
view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)
stackView.frame = view.frame
view.layer.cornerRadius = view.frame.height / 2
view.layer.shadowColor = UIColor(white: 0.4, alpha: 0.4).cgColor
view.layer.shadowRadius = 8
view.layer.shadowOpacity = 0.5
view.layer.shadowOffset = CGSize(width: 0, height: 4)
view.addSubview(stackView)
return view
}()
这里图片就添加好了,接下来要添加HitTesting。 在handleLongPress方法里面添加一个gesture.state == .changed 的判断,并且获取这个手势传给响应方法
else if gesture.state == .changed {
handleGestureChanged(gesture)
}
在响应方法里面获取手势在弹窗视图中的相应位置,然后判断根据hitTest来获取view,如果是UIImageView就将其alpha设为0,这样方便来观察hitTest的效果。这里后运行后可以发现,如果在长按电时候移动到icon上面,那么icon就会消失。
@objc func handleGestureChanged(_ gesture:UILongPressGestureRecognizer){
let pressedLocation = gesture.location(in: iconsContainerView)
let hitTestView = iconsContainerView.hitTest(pressedLocation, with: nil)
if hitTestView is UIImageView {
hitTestView?.alpha = 0
}
}
接下来就为icon添加animation,这里每次移动到icon上面的时候,icon就会向上移动50,当移动到其他地方的时候,就会移动到原来的地方。
if hitTestView is UIImageView {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
let stackView = self.iconsContainerView.subviews.first
stackView?.subviews.forEach({ (imageView) in
imageView.transform = .identity
})
hitTestView?.transform = CGAffineTransform(translationX: 0, y: -50)
}, completion: nil)
}
到这里就差不多完成啦,但是还有个问题就是当松开点击的时候,如果松开的地方在icon上面,那么那个icon就不会移动到原来的位置,这时候就需要在手势结束的地方进行处理。这里会先将icon移回原位后在移除弹窗View。
else if gesture.state == .ended {
// clean up the animation
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
let stackView = self.iconsContainerView.subviews.first
stackView?.subviews.forEach({ (imageView) in
imageView.transform = .identity
})
self.iconsContainerView.transform = self.iconsContainerView.transform.translatedBy(x: 0, y: 50)
self.iconsContainerView.alpha = 0
}, completion: { (_) in
self.iconsContainerView.removeFromSuperview()
})
}
完整代码:
import UIKit
class ViewController: UIViewController {
let iconsContainerView:UIView = {
let view = UIView()
view.backgroundColor = .gray
let iconHeight:CGFloat = 50
let images = [UIImage(named: "cry_laugh"),UIImage(named: "angry"),UIImage(named: "cry"),UIImage(named: "surprised"),UIImage(named: "blue_like"),UIImage(named: "red_heart")]
let arrangedSubviews = images.map { (image) -> UIView in
let imageView = UIImageView(image: image)
imageView.layer.cornerRadius = iconHeight / 2
imageView.isUserInteractionEnabled = true
return imageView
}
let stackView = UIStackView(arrangedSubviews: arrangedSubviews)
stackView.distribution = .fillEqually
let padding:CGFloat = 8
stackView.spacing = padding
stackView.layoutMargins = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
stackView.isLayoutMarginsRelativeArrangement = true
let numIcons = CGFloat(stackView.arrangedSubviews.count)
let width = numIcons * iconHeight + (numIcons + 1) * padding
view.frame = CGRect(x: 0, y: 0, width: width, height: iconHeight + 2 * padding)
stackView.frame = view.frame
view.layer.cornerRadius = view.frame.height / 2
view.layer.shadowColor = UIColor(white: 0.4, alpha: 0.4).cgColor
view.layer.shadowRadius = 8
view.layer.shadowOpacity = 0.5
view.layer.shadowOffset = CGSize(width: 0, height: 4)
view.addSubview(stackView)
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupLongPressGesture()
}
func setupLongPressGesture() {
view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)))
}
fileprivate func handleBegan(_ gesture: UILongPressGestureRecognizer) {
view.addSubview(iconsContainerView)
let pressedLocation = gesture.location(in: view)
let centeredX = (view.frame.width - iconsContainerView.frame.width) / 2
iconsContainerView.transform = CGAffineTransform(translationX: centeredX, y: pressedLocation.y )
iconsContainerView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut) {
self.iconsContainerView.alpha = 1
self.iconsContainerView.transform = CGAffineTransform(translationX: centeredX, y: pressedLocation.y - self.iconsContainerView.frame.height)
}
}
@objc func handleGestureChanged(_ gesture:UILongPressGestureRecognizer){
let pressedLocation = gesture.location(in: iconsContainerView)
//手势只在弹窗里面生效
let hitTestView = iconsContainerView.hitTest(pressedLocation, with: nil)
//手势在整个view里面生效
//let fixedYLocation = CGPoint(x: pressedLocation.x, y: self.iconsContainerView.frame.height / 2)
// let hitTestView = iconsContainerView.hitTest(fixedYLocation, with: nil)
if hitTestView is UIImageView {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
let stackView = self.iconsContainerView.subviews.first
stackView?.subviews.forEach({ (imageView) in
imageView.transform = .identity
})
hitTestView?.transform = CGAffineTransform(translationX: 0, y: -50)
}, completion: nil)
}
}
@objc func handleLongPress(gesture:UILongPressGestureRecognizer) {
if gesture.state == .began {
handleBegan(gesture)
} else if gesture.state == .ended {
// clean up the animation
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
let stackView = self.iconsContainerView.subviews.first
stackView?.subviews.forEach({ (imageView) in
imageView.transform = .identity
})
self.iconsContainerView.transform = self.iconsContainerView.transform.translatedBy(x: 0, y: 50)
self.iconsContainerView.alpha = 0
}, completion: { (_) in
self.iconsContainerView.removeFromSuperview()
})
} else if gesture.state == .changed {
handleGestureChanged(gesture)
}
}
}