Circular transition (mask) in iOS and Objective C - ios

We were wondering how we can easily build a circular mask, that blends out the background and transitions into a new view, with new buttons? See example here (watch the red planet gets triggered):

//Swift 4
This is a simple static method for circular bubble transition from a point screen.
//as shown on this link
https://github.com/andreamazz/BubbleTransition
import UIKit
class AnimationUtility: UIViewController {
static func animateBubbleTrnsitionView( selfView: UIView, point : CGPoint) {
//let button = CGRect.init(x: 30, y: selfView.frame.size.height - 15, width: 45, height: 45)
let button = CGRect.init(x: point.x, y: point.y, width: 0, height: 0)
let circleMaskPathInitial = UIBezierPath(ovalIn: CGRect.init(x: point.x, y: point.y, width: 1, height: 1))
let extremePoint = CGPoint(x: point.x, y: 15 - selfView.frame.size.height - 200)
let radius = sqrt((extremePoint.x*extremePoint.x) + (extremePoint.y*extremePoint.y))
let circleMaskPathFinal = UIBezierPath(ovalIn: (button.insetBy(dx: -radius, dy: -radius)))
let maskLayer = CAShapeLayer()
maskLayer.path = circleMaskPathFinal.cgPath
selfView.layer.mask = maskLayer
let maskLayerAnimation = CABasicAnimation(keyPath: "path")
maskLayerAnimation.fromValue = circleMaskPathInitial.cgPath
maskLayerAnimation.toValue = circleMaskPathFinal.cgPath
maskLayerAnimation.duration = 0.9
maskLayer.add(maskLayerAnimation, forKey: "path")
}
}
Use this code in following way
Perform segue or push without animation.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
switch segue.identifier! {
case "navToHomeWithoutanimation":
self.navigationController?.view.backgroundColor = APP_ORANGE_COLOR //which ever color you want
let vc = segue.destination as! MapViewController
AnimationUtility.animateBubbleTrnsitionView(selfView: vc.view, point: self.view.center)
break
}
}
//The animation will start to animate from given point.

Swift code :
I writted all code appdelegate file. Used a screenshot of timeline rather than a full-blown UITableView.
First, let’s add the screenshot on the window:
let imageView = UIImageView(frame: self.window!.frame)
imageView.image = UIImage(named: "twitterscreen")
self.window!.addSubview(imageView)
self.mask = CALayer()
self.mask!.contents = UIImage(named: "twitter logo mask").CGImage
self.mask!.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
self.mask!.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.mask!.position = CGPoint(x: imageView.frame.size.width/2, y: imageView.frame.size.height/2)
imageView.layer.mask = mask
animating the mask. You’ll observe that the bird reduces in size for a bit, and then increases in size, so to do that using just one animation, let’s use CAKeyframeAnimation.
let keyFrameAnimation = CAKeyframeAnimation(keyPath: "bounds")
keyFrameAnimation.duration = 1
keyFrameAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)]
let initalBounds = NSValue(CGRect: mask!.bounds)
let secondBounds = NSValue(CGRect: CGRect(x: 0, y: 0, width: 90, height: 90))
let finalBounds = NSValue(CGRect: CGRect(x: 0, y: 0, width: 1500, height: 1500))
keyFrameAnimation.values = [initalBounds, secondBounds, finalBounds]
keyFrameAnimation.keyTimes = [0, 0.3, 1]
self.mask!.addAnimation(keyFrameAnimation, forKey: "bounds")
If you see result please visit the github repo. https://github.com/rounak/TwitterBirdAnimation/

Related

What is the proper way to scale a view while animating it with CAKeyframeAnimation and BezierPath

The following code effectively animates and scales a view along a BezierPath but I feel like I'm cheating since I'm mixing UIKit and CAKeyframeAnimation (Core Animation) animations.
Is there a better way to scale a view while animating it along the BezierPath?
func animateIt(){
let myView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
myView.backgroundColor = .blue
myView.layer.cornerRadius = myView.frame.height / 2
view.addSubview(myView)
let startPoint = CGPoint(x: 250, y: 500)
let apexPoint = CGPoint(x: 100, y: 50)
let endPoint = CGPoint(x: 50, y: 350)
let durationTime = 1.5
let path = UIBezierPath()
path.move(to: startPoint)
path.addQuadCurve(to: endPoint, controlPoint: apexPoint)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = durationTime
myView.layer.add(animation, forKey: "bezier")
myView.center = endPoint
UIView.animate(withDuration: durationTime, animations: {
myView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5);
})
}
I tried using a second CAKeyframeAnimation animation instead of the UIKit animation and used the array of key values but no matter what I do, I cannot get a uniform scale, it scales at the beginning but not at the end.
let scaleAnimation = CAKeyframeAnimation(keyPath: "scale")
scaleAnimation.values = [1.0, 0.9, 0.5,]
myView.layer.add(scaleAnimation, forKey: "scale")
Thanks!

UIImage to the Shapelayer in Swift 4

How can we fill a shape layer with image in iOS using swift 4 ? I have tried using below. I got an Black background. Here self is a Shapelayer where i am going to set an Image
let image = UIImage(named: "ACVResources.bundle/temp.png")!
let imageLayer = CALayer()
imageLayer.contents = UIImage(named: "image")?.cgImage // Assign your image
imageLayer.frame = self.frame
imageLayer.mask = self
self.masksToBounds = true
self.setNeedsDisplay()
First, you have to add CALayer(imageLayer) that fill your view after that you need to set that your view.layer mask your shapeLayer.
I give you an example:
let customView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 500))
customView.backgroundColor = .red
view.addSubview(customView)
let imageLayer = CALayer()
let image = UIImage(named: "pxl.jpeg")
imageLayer.contents = image?.cgImage
imageLayer.frame = customView.frame
customView.layer.addSublayer(imageLayer)
let shape = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: customView.frame.size.width / 2, y: 50))
path.addLine(to: CGPoint(x: customView.frame.size.width, y: customView.frame.size.height / 2))
path.addLine(to: CGPoint(x: 0, y: customView.frame.size.height / 2))
path.close()
shape.path = path.cgPath
customView.layer.mask = shape
screenshot of that code

Gradually adding rectangle animation in iOS

I have a single view application in Xcode and doing the following in ViewController.swift. My objective is to have the screen filled up with a brown colored rectangle gradually over 5 seconds. Am I missing something or is the approach completely wrong?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path")
expandAnimation.fromValue = UIBezierPath(rect: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0))
expandAnimation.toValue = UIBezierPath(rect: CGRect(x: 0.0, y: 0.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height))
expandAnimation.duration = 5.0
expandAnimation.fillMode = kCAFillModeForwards
expandAnimation.isRemovedOnCompletion = false
let rectLayer: CAShapeLayer = CAShapeLayer()
rectLayer.fillColor = UIColor.brown.cgColor
rectLayer.path = UIBezierPath(rect: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0)).cgPath
rectLayer.add(expandAnimation, forKey: nil)
self.view.layer.addSublayer(rectLayer)
}
I don't want the color to gradually appear. I want a glass filling kind of effect. Imagine this
except for the fancy wavy effects.
After understanding your question better, you want to simulate the filing of a water glass (without the animation of the wave), you can achieve that through:
let myLayer: CALayer = .init()
myLayer.backgroundColor = UIColor.red.cgColor
self.view.layer.addSublayer(myLayer)
func animateFilling(to view:UIView)
{
var mFrame = CGRect(x: 0, y: view.frame.size.height, width: view.frame.size.width, height: 0)
myLayer.frame = mFrame
let fillingAnim = CABasicAnimation(keyPath: "bounds")
fillingAnim.fillMode = kCAFillModeForwards
fillingAnim.fromValue = mFrame
fillingAnim.duration = 2
fillingAnim.isRemovedOnCompletion = false
mFrame.size.height = 1000
fillingAnim.toValue = mFrame
myLayer.add(fillingAnim, forKey: nil)
}
I think it's easy to do it like that if you don't want top wave
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let vv = UIView(frame: CGRect(x: 0, y: self.view.bounds.size.height, width: self.view.bounds.size.width, height: 0))
vv.backgroundColor = UIColor.brown
self.view.addSubview(vv)
UIView.animate(withDuration: 5.0) {
vv.frame = CGRect(x: 0, y: self.view.bounds.size.height / 3.0, width: self.view.bounds.size.width, height: self.view.bounds.size.height * 2 / 3.0)
}
}

Text in UIView Border

I am currently working on a project in iOS (using XCode and Swift). I am trying to implement the following UITextFields for the login view:
I was thinking of different ways to go about doing this and they all seem complicated. It would be amazing if someone knows of a super easy way to do this or if there is already a cocoapod that can be used to create this TextView.
Here are a few ways I was thinking of doing it:
Just make a UITextField with a border and put a UILabel with a background matching the parent view's background, blocking out the part where "Login" and "Password" would show up. This would hide the border at these parts and would solve the issue. The problem with this approach is if the background is a gradient, pattern, or image. This can be seen in the following images:
If the user looks closely at the "EMAIL" and "PASSWORD" UILabels here it can be seen that it does not have a transparent background and that it has an set background color in order to block out the border of the UITextField.
Instead of doing this, I would like to actually stop the drawing of the border which brings me to a second possible method of implementation.
Using core graphics to manually draw the border of the UITextField, this would have to be dynamic since there can be different length strings ("Login) is 5 characters, "Password" is 8). This approach seems complicated because dealing with CoreGraphics can be annoying.
I wasn't able to come up with any other ways of implementing this but I'd appreciate it if there was a less cumbersome solution.
Try this extension. I have tried this and is working good.
extension UITextField {
func leftBorder() {
let leftBorder = CALayer()
leftBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(1.0), height: CGFloat(self.frame.size.height))
leftBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(leftBorder)
}
func rightBorder() {
let rightBorder = CALayer()
rightBorder.frame = CGRect(x: CGFloat(self.frame.size.width - 1), y: CGFloat(0.0), width: CGFloat(1.0), height: CGFloat(self.frame.size.height))
rightBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(rightBorder)
}
func bottomBorder() {
let bottomBorder = CALayer()
bottomBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(self.frame.size.height - 1), width: CGFloat(self.frame.size.width), height: CGFloat(1.0))
bottomBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomBorder)
}
func topBorder1() {
let topBorder = CALayer()
topBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(25.0), height: CGFloat(1.0))
topBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(topBorder)
}
func topBorder2(position: CGFloat) {
let width = CGFloat(self.frame.size.width - position)
let topBorder2 = CALayer()
topBorder2.frame = CGRect(x: position, y: CGFloat(0), width: width, height: CGFloat(1.0))
topBorder2.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(topBorder2)
}
}
Call those extension methods in viewDidLayoutSubviews method like this..
override func viewDidLayoutSubviews() {
loginTextField.leftBorder()
loginTextField.rightBorder()
loginTextField.bottomBorder()
loginTextField.topBorder1()
let position = CGFloat(25 + loginLabel.frame.size.width + 10)
loginTextField.topBorder2(position: position)
}
This is how the initial story board looks like. I used a textfield and then placed a label above that textfield.
Note: I have used the label's width for some calculation.
And the result in the simulator is
I believe one method of doing this is by drawing the individual borders:
extension UITextField {
func border(position: CGFloat) {
let leftBorder = CALayer()
leftBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(1.0), height: CGFloat(self.frame.size.height))
leftBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(leftBorder)
let rightBorder = CALayer()
rightBorder.frame = CGRect(x: CGFloat(self.frame.size.width - 1), y: CGFloat(0.0), width: CGFloat(1.0), height: CGFloat(self.frame.size.height))
rightBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(rightBorder)
let bottomBorder = CALayer()
bottomBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(self.frame.size.height - 1), width: CGFloat(self.frame.size.width), height: CGFloat(1.0))
bottomBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomBorder)
let topBorder = CALayer()
topBorder.frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat(25.0), height: CGFloat(1.0))
topBorder.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(topBorder)
let width = CGFloat(self.frame.size.width - position)
let topBorder2 = CALayer()
topBorder2.frame = CGRect(x: position, y: CGFloat(0), width: width, height: CGFloat(1.0))
topBorder2.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(topBorder2)
}
I do not think it is possible to do this with a corner radius. The only way that I can imagine doing this is by going into CoreGraphics, which is usually more work than it is worth. Take a look here

How to animate a stitched image?

I'm not sure how to create this animation. Would you somehow split the 1 jpg file evenly in 3 pieces and animate that? Or would you have to make multiple copies of the jpg and do something with them?
Any help would be awesome!
UPDATE
Since you want a crossfade, it's probably easiest to do this by splitting the image into separate cel images:
import UIKit
import PlaygroundSupport
extension UIImage {
func subImage(inUnitRect unitRect: CGRect) -> UIImage? {
guard imageOrientation == .up, let cgImage = self.cgImage else { return nil }
let cgImageWidth = CGFloat(cgImage.width)
let cgImageHeight = CGFloat(cgImage.height)
let scaledRect = CGRect(x: unitRect.origin.x * cgImageWidth, y: unitRect.origin.y * cgImageHeight, width: unitRect.size.width * cgImageWidth, height: unitRect.size.height * cgImageHeight)
guard let croppedCgImage = cgImage.cropping(to: scaledRect) else { return nil }
return UIImage(cgImage: croppedCgImage, scale: scale, orientation: .up)
}
}
let image = #imageLiteral(resourceName: "image.png")
let celCount: CGFloat = 3
let cels = stride(from: 0, to: celCount, by: 1).map({ (i) -> UIImage in
image.subImage(inUnitRect: CGRect(x: i / celCount, y: 0, width: 1/3, height: 1))!
})
Then we can use a keyframe animation to crossfade the layer contents:
let imageView = UIImageView(image: cels[0])
PlaygroundPage.current.liveView = imageView
let animation = CAKeyframeAnimation(keyPath: "contents")
var values = [CGImage]()
var keyTimes = [Double]()
for (i, cel) in cels.enumerated() {
keyTimes.append(Double(i) / Double(cels.count))
values.append(cel.cgImage!)
// The 0.9 means 90% of the time will be spent *outside* of crossfade.
keyTimes.append((Double(i) + 0.9) / Double(cels.count))
values.append(cel.cgImage!)
}
values.append(cels[0].cgImage!)
keyTimes.append(1.0)
animation.keyTimes = keyTimes.map({ NSNumber(value: $0) })
animation.values = values
animation.repeatCount = .infinity
animation.duration = 5
imageView.layer.add(animation, forKey: animation.keyPath)
Result:
ORIGINAL
There are multiple ways you can do this. One is by setting or animating the contentsRect property of the image view's layer.
In your image, there are three cels, and each occupies exactly 1/3 of the image. The contentsRect is in the unit coordinate space, which makes computation easy. The contentsRect for cel i is CGRect(x: i/3, y: 0, width: 1/3, height: 0).
You want discrete jumps between cels, instead of smooth sliding transitions, so you need to use a keyframe animation with a kCAAnimationDiscrete calculationMode.
import UIKit
import PlaygroundSupport
let image = #imageLiteral(resourceName: "image.png")
let celSize = CGSize(width: image.size.width / 3, height: image.size.height)
let imageView = UIImageView()
imageView.frame = CGRect(origin: .zero, size: celSize)
imageView.image = image
PlaygroundPage.current.liveView = imageView
let animation = CAKeyframeAnimation(keyPath: "contentsRect")
animation.duration = 1.5
animation.calculationMode = kCAAnimationDiscrete
animation.repeatCount = .infinity
animation.values = [
CGRect(x: 0, y: 0, width: 1/3.0, height: 1),
CGRect(x: 1/3.0, y: 0, width: 1/3.0, height: 1),
CGRect(x: 2/3.0, y: 0, width: 1/3.0, height: 1)
] as [CGRect]
imageView.layer.add(animation, forKey: animation.keyPath!)
Result:

Resources