Rotation and resizing of UIView created from collection view cell - ios

I have collection view and on tap on its cell uiview is created with coordinates of cell translated to superview. Next I need to resize created uiview to screen size while newly created uiview rotating on 90 degrees:
let coord = collectionView.layoutAttributesForItem(at: indexPath)
let cellFrameInSuperview:CGRect! = collectionView.convert(coord!.frame, to: self.view)
let imageViewDummy = UIView(frame: CGRect(x: (cellFrameInSuperview?.origin.x)!, y: (cellFrameInSuperview?.origin.y)!, width: cell.videoContainer.coverImageView.frame.size.width, height: cell.videoContainer.coverImageView.frame.size.height))
UIView.animate(withDuration: 3, animations: {
var transform = CGAffineTransform.identity
transform = transform.rotated(by: CGFloat(Double.pi / 2))
imageViewDummy.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.height, height: UIScreen.main.bounds.width)
imageViewDummy.transform = transform
})
But after animation, view is wrongly placed. It should fit full screen
UPDATE:
Working code:
UIView.animate(withDuration: 3, animations: {
imageViewDummy.center = CGPoint(x: imageViewDummy.frame.size.height / 2, y: imageViewDummy.frame.size.width / 2)
var transform = CGAffineTransform.identity
transform = transform.rotated(by: CGFloat(Double.pi / 2))
imageViewDummy.transform = transform
imageViewDummy.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}, completion: {(result: Bool) in
})

Related

Animate tab bar shape created using UIBezierPath() based on selected index

I have created a tab Bar shape using UIBezierPath(). Im new to creating shapes so in the end I got the desired shape closer to what I wanted, not perfect but it works. It's a shape which has a wave like top, so on the left side there is a crest and second half has a trough. This is the code that I used to create the shape:
func createPath() -> CGPath {
let height: CGFloat = 40.0 // Height of the wave-like curve
let extraHeight: CGFloat = -20.0 // Additional height for top left and top right corners
let path = UIBezierPath()
let width = self.frame.width
// Creating a wave-like top edge for tab bar starting from left side
path.move(to: CGPoint(x: 0, y: extraHeight)) // Start at top left corner with extra height
path.addQuadCurve(to: CGPoint(x: width/2, y: extraHeight), controlPoint: CGPoint(x: width/4, y: extraHeight - height))
path.addQuadCurve(to: CGPoint(x: width, y: extraHeight), controlPoint: CGPoint(x: width*3/4, y: extraHeight + height))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
Above im using -20 so that shape stays above bounds of tab bar and second wave's trough stays above the icons of tab bar. Here is the desired result:
This was fine until I was asked to animate the shape on pressing tab bar items. So if I press second item, the crest should be above second item and if fourth, then it should be above fourth item. So I created a function called updateShape(with selectedIndex: Int) and called it in didSelect method of my TabBarController. In that im passing index of the selected tab and based on that creating new path and removing old and replacing with new one. Here is how im doing it:
func updateShape(with selectedIndex: Int) {
let shapeLayer = CAShapeLayer()
let height: CGFloat = 40.0
let extraHeight: CGFloat = -20.0
let width = self.frame.width
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: extraHeight))
if selectedIndex == 0 {
path.addQuadCurve(to: CGPoint(x: width / 2, y: extraHeight), controlPoint: CGPoint(x: width / 4, y: extraHeight - height))
path.addQuadCurve(to: CGPoint(x: width, y: extraHeight), controlPoint: CGPoint(x: width / 2 + width / 4, y: extraHeight + height))
}
else if selectedIndex == 1 {
path.addQuadCurve(to: CGPoint(x: width / 2 + width / 4, y: extraHeight), controlPoint: CGPoint(x: width / 4 + width / 4, y: extraHeight - height))
path.addQuadCurve(to: CGPoint(x: width, y: extraHeight), controlPoint: CGPoint(x: width * 3 / 4 + width / 4, y: extraHeight + height))
}
else if selectedIndex == 2 {
let xShift = width / 4
path.addQuadCurve(to: CGPoint(x: width / 2 + xShift, y: extraHeight), controlPoint: CGPoint(x: width / 8 + xShift, y: extraHeight + height))
path.addQuadCurve(to: CGPoint(x: width, y: extraHeight), controlPoint: CGPoint(x: width * 7 / 8 + xShift, y: extraHeight - height))
}
else {
path.addQuadCurve(to: CGPoint(x: width / 2, y: extraHeight), controlPoint: CGPoint(x: width / 4, y: extraHeight + height))
path.addQuadCurve(to: CGPoint(x: width, y: extraHeight), controlPoint: CGPoint(x: width / 2 + width / 4, y: extraHeight - height))
}
path.addLine(to: CGPoint(x: width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.secondarySystemBackground.cgColor
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
And calling it like this:
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let tabBar = tabBarController.tabBar as! CustomTabBar
guard let index = viewControllers?.firstIndex(of: viewController) else {
return
}
tabBar.updateShape(with: index)
}
This is working fine as you can see below but the problem is I learned creating shapes just now and creating that wave based on width of screen so the crest and trough are exactly half the width of frame so I was able to do it for FourthViewController too and got this:
But the problem arises for remaining 2 indices. Im not able to create same wave which looks like the crest is moving above second or third item instead I get something like this:
It doesn't look like other to waves showing the hump over third item. Also my code is strictly based on 4 items and was wondering if Im asked to add 1 more item so tab bar has 5 or 6 items, it would be trouble. Is there any way to update my function that creates new shapes based on index of tab bar or can anyone help me just create shapes for remaining two items? The shape should look same just the hump should exactly be over the selected item.
So you want something like this:
Here's how I did it. First, I made a WaveView that draws this curve:
Notice that the major peak exactly in the center. Here's the source code:
class WaveView: UIView {
var trough: CGFloat {
didSet { setNeedsDisplay() }
}
var color: UIColor {
didSet { setNeedsDisplay() }
}
init(trough: CGFloat, color: UIColor = .white) {
self.trough = trough
self.color = color
super.init(frame: .zero)
contentMode = .redraw
isOpaque = false
}
override func draw(_ rect: CGRect) {
guard let gc = UIGraphicsGetCurrentContext() else { return }
let bounds = self.bounds
let size = bounds.size
gc.translateBy(x: bounds.origin.x, y: bounds.origin.y)
gc.move(to: .init(x: 0, y: size.height))
gc.saveGState(); do {
// Transform the geometry so the bounding box of the curve
// is (-1, 0) to (+1, +1), with the y axis going up.
gc.translateBy(x: bounds.midX, y: trough)
gc.scaleBy(x: bounds.size.width * 0.5, y: -trough)
// Now draw the curve.
for x in stride(from: -1, through: 1, by: 2 / size.width) {
let y = (cos(2.5 * .pi * x) + 1) / 2 * (1 - x * x)
gc.addLine(to: .init(x: x, y: y))
}
}; gc.restoreGState()
// The geometry is restored.
gc.addLine(to: .init(x: size.width, y: size.height))
gc.closePath()
gc.setFillColor(UIColor.white.cgColor)
gc.fillPath()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}
Then, I made a subclass of UITabBarController named WaveTabBarController that creates a WaveView and puts it behind the tab bar. It makes the WaveView exactly twice the width of the tab bar, and sets the x coordinate of the WaveView's frame such that the peak is above the selected tab item. Here's the source code:
class WaveTabBarController: UITabBarController {
let waveView = WaveView(trough: 20)
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if superclass!.instancesRespond(to: #selector(UITabBarDelegate.tabBar(_:didSelect:))) {
super.tabBar(tabBar, didSelect: item)
}
view.setNeedsLayout()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let shouldAnimate: Bool
if waveView.superview != view {
view.insertSubview(waveView, at: 0)
shouldAnimate = false
} else {
shouldAnimate = true
}
let tabBar = self.tabBar
let w: CGFloat
if let selected = tabBar.selectedItem, let items = tabBar.items {
w = (CGFloat(items.firstIndex(of: selected) ?? 0) + 0.5) / CGFloat(items.count) - 1
} else {
w = -1
}
let trough = waveView.trough
let tabBarFrame = view.convert(tabBar.bounds, from: tabBar)
let waveFrame = CGRect(
x: tabBarFrame.origin.x + tabBarFrame.size.width * w,
y: tabBarFrame.origin.y - trough,
width: 2 * tabBarFrame.size.width,
height: tabBarFrame.size.height + trough
)
guard waveFrame != waveView.frame else {
return
}
if shouldAnimate {
// Don't animate during the layout pass.
DispatchQueue.main.async { [waveView] in
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut) {
waveView.frame = waveFrame
}
}
} else {
waveView.frame = waveFrame
}
}
}

Swift: Trying to rotate a drawn shape?

I'm having trouble trying to rotate the shape I have drawn in the following code: I tried using:
triangle.transform = triangle.transform.rotated(by: 3.14159)
but that gives me an error of "value of type 'CATransform3D' has no member 'rotated'". I'm not sure how to rotate the shape.
override func viewDidLoad() {
super.viewDidLoad()
let trianglePath = UIBezierPath()
trianglePath.move(to: CGPoint(x: UIScreen.main.bounds.size.width * 0.5, y: UIScreen.main.bounds.height * 0.5))
trianglePath.addLine(to: CGPoint(x: (UIScreen.main.bounds.size.width * 0.5) + 100, y: (UIScreen.main.bounds.height * 0.5) - 50))
trianglePath.addLine(to: CGPoint(x: (UIScreen.main.bounds.size.width * 0.5) + 100, y: (UIScreen.main.bounds.height * 0.5) + 50))
trianglePath.close()
let triangle = CAShapeLayer()
triangle.path = trianglePath.cgPath
triangle.fillColor = UIColor.gray.cgColor
self.view.layer.addSublayer(triangle)
}
Use this to rotate your triangle, you can use use CGAffineTransform
triangle.setAffineTransform(CGAffineTransform(rotationAngle: .pi))
or using CATransform3D, rotating in Z axis by pi
triangle.transform = CATransform3DMakeRotation(.pi, 0, 0, 1)
but to this work you need to adjust also your CAShapeLayer frame
triangle.frame = self.view.bounds //this do the trick
For animate after it shows
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 0.1) {
triangle.setAffineTransform(CGAffineTransform(rotationAngle: .pi)) //this
triangle.transform = CATransform3DMakeRotation(.pi, 0, 0, 1) //or this
}
}
Hope this helps

Cannot animate UIImageview

My issue is that when I press the button that runs the dealToPlayer function(), everything acts as if a card has been dealt, updates total value of cards dealt, but it does not show any animation of the card being dealt or placed. Why might this be? I am not using auto layout for this scene at all.
func dealToPlayer() {
let index = Int(arc4random_uniform(UInt32(deckCards.count)))
let drawnCard = deckCards.remove(at: index)
randomCards.append(drawnCard)
randomCard = randomCards[3 + cardSelect]
image = UIImageView(frame: CGRect(x: self.deckLocation.x, y: self.deckLocation.y, width: self.width, height: self.height))
image.image = mapping[randomCard]
cardsOut = cardsOut + 1
print("cards out is currently: \(cardsOut)")
cardSelect = cardSelect + 1
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: self.cardTwoLocation.x - (self.cardsOut * self.thirdWidth), y: self.cardTwoLocation.y, width: self.width, height: self.height)
}, completion: nil)
checkPlayerValue()
}
First, make sure your image view is added to your view controller.
Second, make sure your animation is called by putting a break point.
Third, print out the location and make sure the new x and y are valid, and they are different than the previous x and y.
Here is a sample duplicate of your code an it works fine
image = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
image.backgroundColor = .red
self.view.addSubview(image)
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: 200, y: 200, width: 100, height: 100)
}, completion: nil)

UIView transform animations not being called

Currently there is a view I'm trying to translate(move), rotate and scale a view at the same time. For so strange reason it's only scaling it when I put scale at the bottom. But when I change the order and put the scaling first it rotates and translates the view properly but the scale of the view changes briefly to its correct scale before changing back to its original size. I need it to stay in it's scaled form. Here is the code:
class ViewController: UIViewController {
let newView = UIView(frame: CGRect(x: 10, y: 10, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(newView)
UIView.animate(withDuration: 1.0, animations: {
self.newView.transform = CGAffineTransform(translationX: 50, y: 70)//translation
self.newView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)//rotation
self.newView.transform = CGAffineTransform(scaleX: 1, y: 0.5)//scale
})
}
}
Try combine the transforms first, then apply them at a time:
let translate = CGAffineTransform(translationX: 50, y: 70)
UIView.animate(withDuration: 1.0, animations: {
self.view.transform = translate.rotated(by: -CGFloat.pi / 2).scaledBy(x: 1, y: 0.5)
})

Transform UIView within bounds

I am trying to make a view transform and kind of have a circle effect until it reaches certain points then it just fills a rectangle. This is for a material design project I am working on. All the code is in Swift 2.0 on an iOS 8 or above device.
func helloWorld(sender: UIButton) {
let point = sender.frame.origin
let rippleViewInitFrame: CGRect = CGRect(x: point.x, y: point.y, width: 4, height: 4)
let rippleView: UIView = UIView(frame: rippleViewInitFrame)
rippleView.backgroundColor = UIColor.MDColor.blue
let bounds: CGRect = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
rippleView.layer.masksToBounds = true
rippleView.layer.cornerRadius = 2
self.view.addSubview(rippleView)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: {
rippleView.transform = CGAffineTransformMakeScale(200.0, 200.0)
rippleView.bounds = bounds
}, completion: {
finished in
print(rippleView.frame.origin.x)
})
}
Currently the view just grows beyond the size of the screen.
The print statement returns -37176 instead of say 0. I want it to fill the screen and nothing more.
If you want it to fill a rectangle.
1) create a rectangular container UIView.
2) add your rippleView as a subview to this rectangular uiview.
set the clipsToBounds property to yes of your container view.
and just do the animation.
let rectView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100));
rectView.backgroundColor = UIColor.redColor()
// initialRippleView
let rippleView = UIView(frame: CGRect(x: 15, y: 15, width: 2, height: 2));
rippleView.backgroundColor = UIColor.whiteColor()
rippleView.cornerRadius = rippleView.width / 2;
rectView.addSubview(rippleView);
rectView.clipsToBounds = true;
UIView.animateWithDuration(5) { () -> Void in
rippleView.transform = CGAffineTransformMakeScale(100, 100);
}
Try in playground.
import UIKit
import XCPlayground
// the main View
let iPhone = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568));
iPhone.backgroundColor = UIColor.greenColor();
// the rect View that will be the bounds of the animation
let rectView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 150));
rectView.backgroundColor = UIColor.redColor()
rectView.center = iPhone.center;
// initialRippleView
let rippleView = UIView(frame: CGRect(x: 15, y: 15, width: 2, height: 2));
rippleView.layer.cornerRadius = 1;
rippleView.backgroundColor = UIColor.whiteColor()
iPhone.addSubview(rectView);
rectView.addSubview(rippleView);
// this property clips the drawings of subview to be clipped to the bounds of rectView.
rectView.clipsToBounds = true;
UIView.animateWithDuration(5) { () -> Void in
// you may need to calculate the right scale factor
rippleView.transform = CGAffineTransformMakeScale(200, 200);
}
// Playground stuff
XCPShowView("Container View", view: iPhone);
Copy this code in a playground File
Show the Assistant editor
Press play (in the left bottom corner)

Resources