I want to have an UIView to appear by scaling from left to right if I hit a button. If I hit the button again, the view should scale away from right to left.
I found an explanation how to do it for left to right, but even this solution is working only once. If I hide the view and play the animation again it scales it from the centre again.
Also scaling it back from right to left doesn't work either as it disappears immdiatly without any animation.
Here's my code so far:
if(!addQuestionaryView.isHidden)
{
//Reset input if view is hidden
addQuestionaryInput.text = ""
self.addQuestionaryView.isHidden = false
let frame = addQuestionaryView.frame;
let rect = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)
let leftCenter = CGPoint(x: rect.minX, y: rect.minY)
addQuestionaryView.layer.anchorPoint = CGPoint(x:1,y: 0.5)
addQuestionaryView.layer.position = leftCenter
UIView.animate(withDuration: 0.6,
animations: {
self.addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
},
completion: { _ in
self.addQuestionaryView.isHidden = true
})
}
else
{
self.addQuestionaryView.isHidden = false
let frame = addQuestionaryView.frame;
let rect = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.width, height: frame.height)
let leftCenter = CGPoint(x: rect.minX, y: rect.midY)
addQuestionaryView.layer.anchorPoint = CGPoint(x:0,y: 0.5);
addQuestionaryView.layer.position = leftCenter
addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
UIView.animate(withDuration: 0.6,
animations: {
self.addQuestionaryView.transform = CGAffineTransform(scaleX: 1, y: 1)
},
completion: nil)
}
As said the appearing part works once and then starts from the centre again. The disappearing part doesn't work at all.
I don't know what "to scale it so it has a little look like Android reveal animations" means (and I hope I never do; I've never seen an Android phone, I never hope to see one).
But do you mean something like this? This is done by animating a mask in front of the view, thus revealing and then hiding it:
Here's the code used in that example:
class MyMask : UIView {
override func draw(_ rect: CGRect) {
let r = CGRect(x: 0, y: 0, width: rect.width/2.0, height: rect.height)
UIColor.black.setFill()
UIGraphicsGetCurrentContext()?.fill(r)
}
}
class ViewController: UIViewController {
#IBOutlet weak var lab: UILabel!
var labMaskOrigin = CGPoint.zero
var didAddMask = false
override func viewDidLayoutSubviews() {
if didAddMask {return}
didAddMask = true
let mask = MyMask()
mask.isOpaque = false
let w = self.lab.bounds.width
let h = self.lab.bounds.height
mask.frame = CGRect(x: -w, y: 0, width: w*2, height: h)
self.lab.mask = mask
self.labMaskOrigin = mask.frame.origin
}
var labVisible = false
func toggleLabVisibility() {
labVisible = !labVisible
UIView.animate(withDuration: 5) {
self.lab.mask!.frame.origin = self.labVisible ?
.zero : self.labMaskOrigin
}
}
}
Remove this:
addQuestionaryView.layer.anchorPoint = CGPoint(x:0,y: 0.5);
addQuestionaryView.layer.position = leftCenter
addQuestionaryView.transform = CGAffineTransform(scaleX: 0, y: 1)
Then in your animate with duration block use the code below instead of CGAfflineTransform
addQuestionaryView.frame = CGRect(0,0,500,100)
Replace 500 width whatever width you want it to be.
Ans to close it back up do this:
addQuestionaryView.frame = CGRect(0,0,0,100);
Related
I have an icon that I programmatically display.
How can I display a solid, colored disk behind the icon, at the same screen position?
Icon is opaque.
I will sometimes programmatically change screen position and disk's diameter and color.
Here's how I programmatically display the icon now.............
DispatchQueue.main.async
{
ViewController.wrist_band_UIImageView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
}
UPDATE #1 for D.Mika below...
UPDATE #2 for D.Mika below...
import UIKit
import Foundation
class ViewController: UIViewController
{
static var circleView: UIView!
static let wrist_band_UIImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage( systemName: "applewatch" )
theImageView.translatesAutoresizingMaskIntoConstraints = false
return theImageView
}()
override func viewDidLoad()
{
super.viewDidLoad()
view.addSubview( ViewController.wrist_band_UIImageView )
// Init disk image:
ViewController.circleView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
ViewController.circleView.backgroundColor = .red
ViewController.circleView.layer.cornerRadius = view.bounds.size.height / 2.0
ViewController.circleView.clipsToBounds = true
// Add view to view hierarchy below image view
ViewController.circleView.insertSubview(view, belowSubview: ViewController.wrist_band_UIImageView)
App_class.display_single_wearable()
}
}
class App_class
{
static var is_communication_established = false
static var total_packets = 0
static func display_single_wearable()
{
ViewController.wrist_band_UIImageView.frame = CGRect(x: 0, y: 0,
width: 100, height: 100)
ViewController.circleView.frame = CGRect( x: 0,
y: 0,
width: 100,
height: 100)
ViewController.circleView.layer.cornerRadius = 100
}
static func process_location_xy(text_location_xyz: String)
{}
} // App_class
You can display a colored circle using the following view:
circleView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
circleView.backgroundColor = .red
circleView.layer.cornerRadius = view.bounds.size.height / 2.0
circleView.clipsToBounds = true
// Add view to view hierarchy below image view
view.insertSubview(circleView, belowSubview: wrist_band_UIImageView)
You can change it's position and size by applying a new frame. But you have to change the layer's cornerRadius accordingly.
This is a simple example in a playground:
Edit:
I've modified the sample code to add the view the view hierarchy.
BUT, you still need to adjust the circle's frame, when you modify the image view's frame. (and the cornerRadius accordingly). For example
DispatchQueue.main.async
{
ViewController.wrist_band_UIImageView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
circleView.frame = CGRect( x: screen_position_x,
y: screen_position_y,
width: App_class.screen_width,
height: App_class.screen_height)
circleView.layer.cornerRadius = App_class.screen_width
}
And you need to add a variable to the viewController holding the reference to the circle view, like:
var circleView: UIView!
The following answer works.
The answers from d.mika above did not work. Plus, he was insulting. ;-)
// Show red circle
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 200, y: 400), radius: CGFloat(40), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
// Change the fill color
shapeLayer.fillColor = UIColor.red.cgColor
// You can change the stroke color
shapeLayer.strokeColor = UIColor.red.cgColor
// You can change the line width
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
I've got multiple UIImageViews, spread across in a view.
#IBOutlet var leftIVs: [UIImageView]!
#IBOutlet var topIVs: [UIImageView]!
#IBOutlet var rightIVs: [UIImageView]!
I'm trying to create a function using UIView.animate... functions and 'transform' property which brings all of these UIImageViews at the center of the superview. I'm using the following code:
let performInitialTransformation: ((UIImageView)->()) = { (card) in
let cardCenter = CGPoint(x: card.frame.midX, y: card.frame.midY)
let viewCenter = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY)
let deltaPoint = cardCenter - viewCenter //also tried (viewCenter - cardCenter)
UIView.animate(withDuration: 0.5, animations: {
card.transform = CGAffineTransform.init(translationX: deltaPoint.x, y: deltaPoint.y)
}) { (done) in
}
}
for card in topIVs {
performInitialTransformation(card)
}
for card in leftIVs {
performInitialTransformation(card)
}
for card in rightIVs {
performInitialTransformation(card)
}
I'm using this static function:
extension CGPoint {
static func -(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
return CGPoint(x: rhs.x - lhs.x, y: rhs.y - lhs.y)
}
}
NOTE: Also, I will be bringing those images back to there original position afterward for which I will use CGAffineTransform.identity
The images are not being shown in the center. How can I achieve this? Thanks in advance!
I think your problem was that you didn't reduce the size of the image view so that they stack at the exact centre of the screen.
let deltaX = (self.view.center.x - card.frame.minX) - card.frame.width/2
let deltaY = (self.view.center.y - card.frame.minY) - card.frame.height/2
UIView.animate(withDuration: 1) {
card.transform = .init(translationX: deltaX, y: deltaY)
}
What is about to use center
UIView.animate(withDuration: 0.5) {
images.forEach { $0.center = superview.center }
}
The full example
var subviewes = [UIView]()
var view = UIView(frame: CGRect.init(x: 0, y: 0, width: 30, height: 30))
view.backgroundColor = .yellow
subviewes.append(view)
view = UIView(frame: CGRect.init(x: 0, y: 0, width: 20, height: 20))
view.backgroundColor = .green
subviewes.append(view)
view = UIView(frame: CGRect.init(x: 0, y: 0, width: 10, height: 10))
view.backgroundColor = .red
subviewes.append(view)
subviewes.forEach { self.view.addSubview($0) }
UIView.animate(withDuration: 0.5) {
subviewes.forEach { $0.center = self.view.center }
}
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)
})
I've been trying to make a loading screen similar to Twitter's loading overlay here:
I'm not sure if the way I've approached this is the "right" way to do things at all. My concerns are many:
Is there a better way to pick the mask image I'm using for various screen sizes? The mask needs to extend to the edges of the screen to work with the background color, so I'm making it the size of the phone's possible bounds.
Are animations being handled correctly? This is my first use of CATransaction for anything.
Should I be adding the view to the tab bar controller's view like I'm doing?
Is there any way to abstract this out, or a better place to put this in the app's lifecycle? As of now I need to duplicate this code and place it any controller that could be a starting point (two controllers as of now).
You can see what I've written so far below:
override func viewDidLoad() {
super.viewDidLoad()
prepareLoadingOverlay()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
animateMask()
}
var mask: CALayer?
var blueView: UIImageView?
func prepareLoadingOverlay() {
let size = UIScreen.mainScreen().bounds.size
var cutoutImage: UIImage?
switch size.height {
case 420:
cutoutImage = CutoutMap.Image480 // UIImage
case 568:
cutoutImage = CutoutMap.Image568
case 667:
cutoutImage = CutoutMap.Image667
case 736:
cutoutImage = CutoutMap.Image736
default:
cutoutImage = CutoutMap.Image667
}
if let tabBarViewController = self.tabBarController,
let cutout = cutoutImage {
let containerFrame = tabBarViewController.view.frame
blueView = UIImageView.init(frame: containerFrame)
blueView!.image = UIImage.imageFromColor(Color.Blue)
self.mask = CALayer()
self.mask?.contents = cutout.CGImage
self.mask?.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
self.mask?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.mask?.position = CGPoint(x: containerFrame.size.width/2, y: containerFrame.size.height/2)
tabBarViewController.view.addSubview(blueView!)
blueView!.layer.mask = self.mask
}
}
func animateMask() {
let size = UIScreen.mainScreen().bounds.size
if let mask = self.mask {
CATransaction.begin()
CATransaction.setAnimationDuration(1)
CATransaction.setCompletionBlock { () -> Void in
CATransaction.begin()
CATransaction.setAnimationDuration(1)
CATransaction.setCompletionBlock { () -> Void in
self.blueView?.removeFromSuperview()
}
mask.bounds = CGRect(x: 0, y: 0, width: size.width*15, height: size.height*15)
CATransaction.commit()
}
mask.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
CATransaction.commit()
}
}
Any help on this would be greatly appreciated!
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)