Today I want to show how to use the GravitySDK 🚀 (as discussed in the previous post) to make an animated bubbles buttons that falling around with gravity, like this:
Let’s start!
First of all, add as Swift Package the “Gravity” SDK 😎, importing the package:
https://github.com/elpsk/Gravity
And imports in your project the newest SDK:
import GravitySPM
BubbleView
Create a new BubbleView class that extends from UIView.
In order to match exactly the circle view borders you should use a CAShapeLayer instead of a rounded view.
The difference is that using UIView with corner radius you have the view borders that collide with other views:
UIView with corderRadius UIView with CAShapeLayer
Code!
class BubbleView: UIView {
var shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = UIColor.clear.cgColor
_shapeLayer.allowsEdgeAntialiasing = true
_shapeLayer.backgroundColor = UIColor.clear.cgColor
return _shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.bounds.width / 2
self.backgroundColor = .white
layer.addSublayer(shapeLayer)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
shapeLayer.path = circularPath(lineWidth: 0, center: center).cgPath
}
private func circularPath(lineWidth: CGFloat = 0, center: CGPoint = .zero) -> UIBezierPath {
let radius = (min(bounds.width, bounds.height) - lineWidth) / 2
return UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
}
}
This class create a UIView with a circular CAShapeLayer.
This is not enough because the collision type of this class must be setted to “path“, overriding the collisionBoundsType 🚧 and the collisionBoundingPath:
override var collisionBoundsType: UIDynamicItemCollisionBoundsType {
return .path
}
override var collisionBoundingPath: UIBezierPath {
return circularPath()
}
In this way the collision respect exactly the circle borders!
Create the Bubbles 🎾🏀!
Now go into your ViewController and prepare the UI:
import GravitySPM
class ViewController: UIViewController {
var circles: [BubbleView] = []
var gravity: Gravity?
var gravityItems: [UIDynamicItem] = []
override func viewDidLoad() {
super.viewDidLoad()
// create random BubbleView in random position
for idx in 0..<6 {
let randX = Int.random(in: 100..<Int(self.view.frame.size.width - 100))
let randY = Int.random(in: 100..<Int(self.view.frame.size.height - 100))
let bubble = BubbleView(
frame: CGRect(x: randX, y: randY, width: 145, height: 145),
parentBound: self.view.bounds,
size: 145,
expansionDelta: 30,
padding: 30
)
bubble.delegate = self
bubble.tag = idx.offset
self.view.addSubview(bubble)
}
// prepare the bubbles to pass to SDK
gravityItems = self.view.subviews.filter{ $0 is BubbleView }
gravity = Gravity(
gravityItems: gravityItems, // <<-- your bubbles
collisionItems: nil,
referenceView: self.view,
boundary: UIBezierPath(rect: self.view.frame),
queue: nil)
// start gravity
gravity?.enable()
}
}
You’re done!
Build and run and you should see the bubbles falling around your main view!
📌 NOTE
Remember to lock the orientation of your app…
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
EXTRA
If you want to make the bubbles touchable and scaled when touched, add into the BubbleView class the touched protocol:
protocol BubbleViewDelegate {
func didViewTouched( view: BubbleView )
}
and implement the touchesEnded delegate:
var delegate: BubbleViewDelegate?
[...]
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
selected.toggle()
shapeLayer.fillColor = selected ? UIColor.red.cgColor : UIColor.white.cgColor
self.backgroundColor = selected ? UIColor.clear : UIColor.white
if selected {
self.bounds.size.width = defaultSize! + expansionSize!
self.bounds.size.height = self.bounds.size.width
} else {
self.bounds.size.width = defaultSize!
self.bounds.size.height = self.bounds.size.width
}
self.delegate?.didViewTouched(view: self)
}
Next, in your ViewController implement the protocol:
extension ViewController: BubbleViewDelegate {
func didViewTouched(view: BubbleView) {
let viewIndex = gravityItems.firstIndex { item in
if let vitem = item as? BubbleView {
return vitem.tag == view.tag
}
return false
}
if viewIndex != NSNotFound {
self.gravityItems.remove(at: viewIndex!)
self.gravityItems.append(view)
}
// remember to restart the gravity animator
// in order to re-layout all views with borders.
// Of course, only if the size change else is useless...
gravity?.restart()
}
}
Final result 🎥:
Enjoy and share the bubbles! 🎉🎉🎉